【问题标题】:HashMap in Java, 100 Million entriesJava中的HashMap,1亿个条目
【发布时间】:2011-05-04 01:58:15
【问题描述】:

我想将 1 亿个术语及其频率(在文本数据库中)存储到 HashMap <String, Double> 中。它给了我“内存不足”错误。我试图将堆空间增加到-Xmx15000M。但是它运行了半个小时,然后再次抛出相同的异常。我试图从中读取单词和频率的文件大小为 1.7GB。

任何帮助将不胜感激。

谢谢 :-)

【问题讨论】:

  • 您运行的是 32 位还是 64 位 JVM?
  • 你到底在做什么需要 1 亿个术语?您在为 Google 工作吗?
  • 一开始为什么要把它存储在HashMap中呢?正如许多人建议您可以将其存储在数据库中一样,您可能想要映射减少它(Hadoop?)。虽然这完全取决于为什么 HashMap。
  • 有多少不同的术语?如果有很多重复,那么虽然数据量对于内存来说太大了,但频率表仍然可能是一个合理的大小。那样的话,只是分阶段处理完整文件的问题……

标签: java hashmap


【解决方案1】:

对于这样的文字处理,如果您可以忍受更长的查找时间,答案通常是树而不是哈希图。这种结构对于自然语言来说非常节省内存,因为许多单词都有共同的起始字符串。

根据输入,Patricia 树可能会更好。

(另外,如果这确实是来自自然语言的词,你确定你真的需要 100,000,000 个词条吗?大多数常用词都出奇地低,商业解决方案(词预测、拼写校正)很少使用超过 100,000 个词与语言无关。)

【讨论】:

  • 我尝试了 Patricia trie。这次我达到了 GC 限制,15GB 内存仍然不够。 :-)
  • 它为我指出了其他解决方案,并且我学习了一个新的库工具。很难选择最好的,而所有答案都指出了一些有用的东西。最好的答案是一个,但我非常感谢这里所有认真的回答者。我希望我可以选择多个最佳答案...
  • 斯洛伐克语有 1,000,000 个单词,因为我们经常使用变形
【解决方案2】:

在java中,一个对象至少有16个字节的开销 在考虑它包含哪些其他内容之前,请先调整大小。

哈希图中的 1e8 个项目的大小要求被低估了 1e8 * 2 * 16 字节,这是假设您的密钥和 值是数字,因此需要几 GB 的可用堆 在您的堆中和从您的计算机中。

字符串是一个包含字符数组的对象,因此您的字符串 正如上面许多人提到的,可能比 Double 对象大 例如,因此您需要更多可用内存 堆。

请注意,当您接近极限时,程序开始表现不佳 你的电脑也是。

如果您不想使用上述建议的数据库, 你可以考虑编码和压缩你的密钥来制作 它们变成你仍然可以计算频率的数字。 您可以选择基于熵的编码 第一次编码中单词的频率,然后从那里开始......

【讨论】:

    【解决方案3】:

    一个 1.7 GB 的文件是一个相对较小的文件,用于执行此操作并存储在 RAM 中。我使用更大的文件执行此操作并将它们存储在内存中没有问题。可以使用数据库,但根据您计划对数据执行的操作,可能会过度使用或可能是完美的。

    正如其他人所说,使用自然语言时,唯一值的数量可能相对较少,因此地图实际上不会变得那么大。我不会使用 java.util.HashMap,因为它是 very inefficient in terms of memory 的用法,尤其是在存储原始值(如整数)时。 java.util.HashMap 将原语存储为对象。它还将每个值存储在 HashMap.Entry 对象中,这会浪费内存。由于这两个因素,java.util.HashMap 使用的内存比Trove、Fastutil 等替代方案要多得多:

    如前所述,有几个地图实现不存在这些问题。由于您在地图中存储数字,因此额外的好处是您将获得性能提升,因为在您将新值放入地图或更新旧值时无需在对象和基元之间不断切换(即装箱/拆箱)价值观。可以找到更适合大量数据的各种原始哈希图的基准on this post at the Java Performance Tuning Guide:

    【讨论】:

      【解决方案4】:

      您的问题是 1.7 GB 原始文本超过 1500 MB,即使没有单个字符串对象增加的开销。对于大型映射,您应该使用数据库或文件支持的 Map,它们将使用磁盘内存而不是堆。

      更新

      我认为对于大多数 jvm 来说,为堆分配 15 GB 是不可能的。它不适用于任何 32 位 jvm,我不认为 64 位 jvm 也可以。 当有足够的 RAM 可用时,15 GB 的内存应该可以在 64 位 jvm 上运行。

      【讨论】:

      • @nos 最高可达 3.4 GB
      • 您可以将数据库放在内存中以加快速度,但是是的,数据库会更加典型。
      • @josefk:我知道这篇文章已经过时了,但您可以为单个 JVM 进程分配超过 15G 的 RAM。我已经尝试到 25GB 并且它可以工作。规格:64 核机器,64 GB RAM 和 Sun JDK 6。
      • @Sanjay T. Sharma 很高兴知道,我无法访问 64 位系统,所以我无法检查它并错误地认为堆大小会由于系统或 jvm 限制而受到限制.
      • 您不需要将原始文本存储在内存中。您只需要存储应该少几个数量级的数据百万和相应的 int 的唯一术语。根据输入数据,我们可能会谈论 100,000 个术语,或者最多 100 万个,这很容易在现代计算机中存储。
      【解决方案5】:

      信封背面: 1.7Gb/100M = 平均 18 字节 = 每项和频率

      我们可以使用由两个逻辑数组支持的手工编码哈希图。

      1. 一个是保存int频率(值),另一个是构建一个C风格的char数组来模拟一个二维的c数组(一个char数组的数组)。所以我们通过计算索引。我们不能使用 java 二维数组,因为它带有太多的对象开销。这个 char 数组可以保存固定大小的 char 数组来表示键。所以我们计算密钥的哈希并将其放入这个“二维数组”中,如果我们有冲突,可以通过线性探测来解决。键值对由数组的公共索引绑定。

      2. hashmap 必须使用开放寻址,因为我们没有足够的内存进行链接。

      3. 根据键的长度,我们可以说这个 hashmap 的 10 个实例;因为不知道数据的特性所以不能确定。

      4. 已用空间 = int 数组的 2 次方 29 +(2 次方 4(每个字符串 16 个字节)* 2 pow 27)= 3.5 gig

      5. 如果我们想要双倍频率而不是整数,那么我们可能需要适当地减小字符串的大小。

      【讨论】:

        【解决方案6】:

        Terracotta 提供了有趣的产品 - BigMemory,这似乎正是您想要的。我自己没有尝试过,也不知道许可条款等。

        【讨论】:

          【解决方案7】:

          考虑将其替换为 cdb。高达 4 GB 并且:

          在大型数据库中成功查找通常只需要两次磁盘访问。不成功的查找只需要一个。

          【讨论】:

            【解决方案8】:

            您也可以尝试使用词干提取来增加重复项的数量。

            例如, 猫=猫=猫=猫

            或

            游泳=游泳=游泳

            尝试谷歌搜索“Porter Stemmer”

            【讨论】:

              【解决方案9】:

              删除 HashMap 并将所有这些数据加载到 HBase 或其他 NoSQL 数据存储之一,并根据 MapReduce 操作编写查询。这是谷歌搜索和许多其他处理大量数据的网站所采用的方法。事实证明,它基本上可以扩展到无限大。

              【讨论】:

              【解决方案10】:

              如果您只想要一个轻量级的 KeyValue (Map) 存储,我会考虑使用 Redis。它非常快,并且能够在需要时持久化数据。唯一的缺点是您需要在 linux 机器上运行 Redis 存储。

              如果你仅限于 Windows,如果可以在 64 位上运行,MongoDB 是一个不错的选择。

              【讨论】:

              • 但是用redis和java好像有点复杂?
              • Redis 现在也兼容 Windows :)
              【解决方案11】:

              其他答案已经指出问题在于内存使用情况。根据您的问题域,您可以设计一个减少整体内存占用的关键类。例如,如果您的密钥由自然语言短语组成,您可以将组成该短语的单词分离和实习;例如

              public class Phrase {
                private final String[] interned;
              
                public Phrase(String phrase) {
                  String[] tmp = phrase.split(phrase, "\\s");
              
                  this.interned = new String[tmp.length];
              
                  for (int i=0; i<tmp.length; ++i) {
                    this.interned[i] = tmp[i].intern();
                  }
                }
              
                public boolean equals(Object o) { /* TODO */ }
                public int hashCode() { /* TODO */ }
              }
              

              事实上,即使字符串不代表自然语言,此解决方案也可能有效,前提是字符串之间存在可利用的大量重叠。

              【讨论】:

                【解决方案12】:

                这是一个糟糕的设计。在 HashMap 上的内存中有 1.7GB 的数据,我会做这两个中的任何一个:

                1. 保留所有数据(文件/数据库)并在内存中保留前 1% 或其他内容。使用某种算法来确定哪些 ID 将在内存中以及何时在内存中。

                2. 使用memcached。最简单的出路。内存中分布式哈希。这正是 DHT 的用途。

                【讨论】:

                  【解决方案13】:

                  对于失败的原因,我同意上面的答案。

                  DB 是不错的选择。但即使是商业级别的 DB,他们也会建议对数据进行“分区”以执行有效的操作。

                  根据您的环境,我可能会建议使用通过 LAN 连接的多个节点分布您的数据。基于Key值,

                  节点 01 的键以 'a' 开头 节点 02 的键以“b”开头....

                  所以你的程序突然变成了网络编程..

                  【讨论】:

                  • 哦,来吧。 1 亿行对于适当的数据库服务器来说是很小的变化。在这里进行分区毫无意义。我的表的数据量是该表的 10 倍,没有任何性能问题。
                  【解决方案14】:

                  如果有 1 亿个术语,您几乎可以肯定超过了应存储在内存中的限制。将您的术语存储在某种数据库中。要么使用商业数据库,要么编写一些允许您访问文件以获取所需信息的内容。如果您拥有的文件格式不允许您快速访问文件,则将其转换为可以访问的文件格式 - 例如将每条记录设为固定大小,这样您就可以立即计算任何记录编号的文件偏移量。然后对记录进行排序将允许您非常快速地进行二进制搜索。您还可以编写代码来大大加快对文件的访问速度,而无需将整个文件存储在内存中。

                  【讨论】:

                    【解决方案15】:

                    Trove THashMap 使用更少的内存。不过,怀疑这是否足以缩小尺寸。除了严格存储在内存中之外,您还需要在其他地方存储这些信息以供检索。

                    【讨论】:

                    • 请不要推荐 Trove,有更好的选择:java-performance.info/…
                    • @leventov 你知道这个答案已经六岁了,对吧?在当时,这是一个不错的选择。有更新的信息很好,但只是说出来。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-12-13
                    • 2016-07-29
                    • 2020-05-21
                    • 1970-01-01
                    • 2013-11-22
                    • 1970-01-01
                    相关资源
                    最近更新 更多