【问题标题】:The best way to store and access 120,000 words in java在java中存储和访问120,000个单词的最佳方式
【发布时间】:2010-10-05 20:36:21
【问题描述】:

我正在编写一个严格读取文本文件 (.txt) 的 Java 应用程序。这些文件可以包含超过 120,000 个单词。

应用程序需要存储所有 +120,000 个单词。它需要将它们命名为 word_1、word_2 等。它还需要访问这些词以对它们执行各种方法。

这些方法都与字符串有关。例如,将调用一个方法来说明 word_80 中有多少个字母。将调用另一种方法来说出 word_2200 中的特定字母。

另外,有些方法会比较两个词。例如,将调用一个方法来比较 word_80 和 word_2200 并需要返回哪个有更多的字母。将调用另一个方法来比较 word_80 和 word_2200 并需要返回两个词共享的特定字母。

我的问题是:由于我几乎只使用字符串,最好将这些单词存储在一个大的 ArrayList 中吗?几个小的 ArrayList?或者我应该使用许多其他存储可能性中的一种,例如 Vectors、HashSets、LinkedLists?

我主要关心的两个问题是 1.) 访问速度,以及 2.) 拥有尽可能多的预构建方法供我使用。

提前感谢您的帮助!!


哇!感谢大家对我的问题提供如此快速的答复。你所有的建议都对我帮助很大。我正在考虑并考虑您反馈中提供的所有选项。

请原谅我的任何模糊;让我来解决你的问题:

  1. 问)英语?
    A)文本文件实际上是用英文写的书。在第二语言中出现的单词是罕见的——但并非不可能。我将文本文件中非英语单词的百分比设置为 .0001%

  2. 问)家庭作业?
    A)我现在正在微笑地看着我的问题的措辞。是的,它确实类似于学校作业。但是不,这不是家庭作业。

  3. Q) 重复?
    一)是的。考虑到连词、冠词等,大概每五个左右的词。

  4. Q) 访问权限?
    A)随机和顺序。一种方法当然有可能随机定位一个单词。一个方法同样可能希望在 word_1 和 word_120000 之间按顺序查找匹配的单词。这就引出了最后一个问题……

  5. Q) 遍历整个列表?
    A) 是的。

另外,我计划发展这个程序以对单词执行许多其他方法。我再次为我的模糊性道歉。 (细节确实会改变世界,不是吗?)

干杯!

【问题讨论】:

  • 当您说单词时,您是指正常的英语单词吗?每个平均大约 5-6 个字符,最大长度大约 30 个字符左右?
  • 嗯...听起来像是家庭作业?如果是这样,这应该被标记。
  • 我仍然认为这里没有足够的细节来提供真正好的建议。你实际上想要完成什么?与其只描述数据,还不如描述你试图用它做什么。当你的代码从数据结构中访问一个词时,它对那个词做了什么?
  • 另外,无论您如何存储数组,请记住在从文本文件中读取每个字符串时对每个字符串进行 intern(),这样重复不会最终导致实际复制字符数据。

标签: java storage


【解决方案1】:

使用Hashtable?这将为您提供最佳的查找速度。

【讨论】:

  • 如果单词只需要通过索引访问,数组会给出最好的查找速度。
  • 确实如此。但是,如果需要基于任意一组键以非顺序访问它们,则 Hashtable(甚至更好的是 HashMap)会更有效。我想答案取决于他的应用程序中的哪一个。
  • 哈希仅在 OP 打算通过任何键查找但索引时才有用。 OP 建议只需要索引即可
【解决方案2】:

我会将它们存储在一个大的 ArrayList 中,然后担心(可能是不必要的)优化。

天生懒惰,我认为优化不是一个好主意,除非有明显的需要。否则,您只是在浪费本可以更好地花在其他地方的精力。

事实上,如果你可以为你的字数设置一个上限并且你不需要任何花哨的 List 操作,我会选择一个普通的(本机)字符串对象数组,其中包含一个包含 实际号码。这可能比基于类的方法更快。

这为您提供了访问单个元素的最快速度,同时仍然保留了执行所有精彩字符串操作的能力。

注意,我没有针对 ArrayLists 对原生数组进行基准测试。它们可能和原生数组一样快,所以如果你对我的能力没有我那么盲目相信的话,你应该自己检查一下:-)。

如果它们确实结果同样快(甚至接近),那么额外的好处(例如可扩展性)可能足以证明它们的使用是合理的。

【讨论】:

  • 我不太确定普通的香草数组是否全面优于 ArrayLists。 Java 中的对象创建非常便宜,并且您可以在列表上方获得一组非常好的抽象来帮助您。配置正确,我认为没有理由使用普通数组。您应该这样做的唯一原因是,如果分析显示绝对准确,您可以获得更好的性能。我怀疑你会从分析中得到这些结果。
  • Yuval,我大体上同意你的所有观点,但在我看来,在这种情况下 ArrayList 被用来简单地存储固定数量的 String 对象,所以没有花哨的 ArrayList 功能将很有用。重要的是 String 函数。我很认真地测试它,我最喜欢的口头禅是“衡量,不要猜测”。
  • 我认为您关于过早优化的观点是正确的,我支持这些 cmets。
【解决方案3】:

如果您要按顺序访问这些字符串,LinkedList 将是最佳选择。

对于随机访问,ArrayLists 具有良好的内存使用/访问速度折衷。

【讨论】:

  • 按顺序访问数组(可能还有 ArrayLists)仍然更快。链表给你的是快速插入和删除(这里可能需要也可能不需要)。
  • @Pax:有时不可能在连续的内存块上分配 120.000 个字符串(虽然我不知道 JVM 是否会处理这个问题,但我不是 Java 专家)。从这个意义上说,链表更好——内存不需要是连续的,因此更容易分配。
  • @fs,200 个 30 个字符的字符串数组不需要 6000 个连续字符,只需要 200 个连续指针。字符串在别处是新的。
  • @fsanches:完全错误。 JVM 不使用 malloc,也不保留空闲列表。 JVM 总是从堆顶分配。连续的 120,000 个分配(没有中间分配)将总是在堆内存中相邻。但是,更重要的是,为什么这很重要?
  • @benjismith:我说的是分配一个包含 120000 个元素的简单数组。如果操作系统不能连续分配这个空间,分配就会失败。链接列表不会遇到这个问题。
【解决方案4】:

只是用一个非常幼稚的基准来确认 pax 假设

public static void main(String[] args)
{
    int size = 120000;
    String[] arr = new String[size];
    ArrayList al = new ArrayList(size);
    for (int i = 0; i < size; i++)
    {
        String put = Integer.toHexString(i).toString();
        // System.out.print(put + " ");
        al.add(put);
        arr[i] = put;
    }

    Random rand = new Random();
    Date start = new Date();
    for (int i = 0; i < 10000000; i++)
    {
        int get = rand.nextInt(size);
        String fetch = arr[get];

    }
    Date end = new Date();
    long diff = end.getTime() - start.getTime();
    System.out.println("array access took " + diff + " ms");

    start = new Date();
    for (int i = 0; i < 10000000; i++)
    {
        int get = rand.nextInt(size);
        String fetch = (String) al.get(get);

    }
    end = new Date();
    diff = end.getTime() - start.getTime();
    System.out.println("array list access took " + diff + " ms");
}

和输出:
数组访问耗时 578 毫秒
数组列表访问耗时 907 毫秒

运行几次,实际时间似乎有所不同,但通常数组访问快 200 到 400 毫秒,迭代次数超过 10,000,000 次。

【讨论】:

  • 实际上,这比我想象的要小(10m 迭代的时间是两倍,但只有 1/2 秒)。以最低的成本获得 ArrayList 的好处可能是值得的。
  • 毫秒还是微秒?如果这些是永恒的!
  • @wf,它是毫秒(对于那些 Unicode 不足的鞋底,微将被写入 200us)。对于 1000 万次迭代来说,五分之一秒的差异还算不错
  • 有趣的基准测试。您使用 Date 对象而不是 System.nanoTime() 的任何原因?
  • 现在使用 Object[] 和循环内的强制转换进行基准测试,就像使用 ArrayList 完成的一样!
【解决方案5】:

ArrayList/Vector 如果顺序很重要(它似乎很重要,因为您正在调用单词“word_xxx”),或者 HashTable/HashMap 如果它不重要。

我将把弄清楚为什么要使用 ArrayList 与 Vector 或 HashTable 与 HashMap 的练习留给您,因为我偷偷怀疑这是您的作业。检查 Javadocs。

您不会从 Collections Framework 类中获得上述示例中所要求的任何方法,因为它们都不执行字符串比较操作。除非您只想按字母顺序或其他方式对它们进行排序,否则您将使用 Collections 框架中的 Tree 实现之一。

【讨论】:

    【解决方案6】:

    我不明白为什么这么多人建议使用 Arraylist 等,因为您没有提到必须遍历整个列表。此外,您似乎希望以键/值对(“word_348”="pedantic")的形式访问它们。

    为了获得最快的访问速度,我会使用 TreeMap,它会进行二进制搜索以找到您的密钥。它唯一的缺点是它是不同步的,但这对您的应用程序来说不是问题。

    http://java.sun.com/javase/6/docs/api/java/util/TreeMap.html

    【讨论】:

    • 您希望使用 ArrayList 或 Array 来利用随机访问。如果您正在迭代,使用 LinkedList 可能更有意义。
    • TreeMap 将比数组或 ArrayList 慢得多。请记住,TreeMap 提供 O(log n) 访问时间,而数组和 ArrayList 提供 O(1) 访问时间。
    【解决方案7】:

    取决于问题所在 - 速度或内存。

    如果是内存,最小的解决方案是写一个函数getWord(n),每次运行时扫描整个文件,并提取单词n。

    现在 - 这不是一个很好的解决方案。一个更好的解决方案是决定你想使用多少内存:假设 1000 个项目。应用程序启动时扫描文件中的单词一次,并存储一系列书签,其中包含单词编号和文件中它所在的位置 - 这样做的方式是使书签或多或少均匀分布文件。

    然后,打开文件进行随机访问。函数 getWord(n) 现在查看书签以找到最大的单词 #

    使用更多内存的更快解决方案是为块构建某种缓存 - 基于 getWord() 请求通常在集群中通过。您可以进行调整,以便如果有人要单词 #X,但它不在书签中,那么您可以寻找它并将其放入书签中,通过合并最近最少使用的书签来节省内存。

    等等。实际上,这取决于问题所在 - 取决于可能的检索模式。

    【讨论】:

      【解决方案8】:

      基数树或帕特里夏树怎么样?

      http://en.wikipedia.org/wiki/Radix_tree

      【讨论】:

        【解决方案9】:

        我的看法:

        对于非线程程序,Arraylist 总是最快和最简单的。

        对于线程程序,java.util.concurrent.ConcurrentHashMap 或 java.util.concurrent.ConcurrentSkipListMap 非常棒。或许您稍后希望允许线程,以便同时对这个巨大的事物进行多个查询。

        【讨论】:

        • 只要你在启动时初始化列表,完成它并且之后不要改变它,一个ArrayList对于多线程来说是可以的。
        【解决方案10】:

        与数组或数组列表相比,链表的唯一优点是可以在任意位置进行插入和删除。我不认为这里是这种情况:您阅读文档并按顺序构建列表。

        我认为当原始发帖人谈到查找“word_2200”时,他的意思只是文档中的第 2200 个单词,而不是每个单词都有任意标签。如果是这样,那么他所需要的只是对所有单词的索引访问。因此,一个数组或数组列表。如果真的有更复杂的东西,如果一个词可能被标记为“word_2200”而下一个词被标记为“foobar_42”或类似的,那么是的,他需要一个更复杂的结构。

        嘿,你想告诉我们你为什么要这样做吗?我很难记得上次我对自己说:“嘿,我想知道我正在阅读的这份文件中的第 1,237 个单词是比第 842 个单词长还是短?”

        【讨论】:

          【解决方案11】:

          如果您想要快速遍历和紧凑的大小,请使用 DAWG(有向无环词图)。这种数据结构采用了 trie 的概念,并通过查找和分解常见的后缀以及常用前缀。

          http://en.wikipedia.org/wiki/Directed_acyclic_word_graph

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-08
            • 2015-04-30
            • 2016-09-18
            • 2019-01-03
            • 1970-01-01
            相关资源
            最近更新 更多