Java中的HashMap,1亿条目

时间:2010-11-02 17:26:26

标签: java hashmap

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

非常感谢任何帮助。

谢谢:-)

15 个答案:

答案 0 :(得分:16)

对于像这样的文字处理,答案通常是树而不是散列图,如果你可以忍受较长的查找时间。对于自然语言来说,这种结构非常有效,因为很多单词都有共同的起始字符串。

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

(另外,如果这确实是来自自然语言的单词,你确定你真的需要100,000,000个条目吗?大多数常用单词都非常低,商业解决方案(单词预测,拼写纠正)很少使用超过100,000个单词不管语言如何。)

答案 1 :(得分:11)

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

<强>更新

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

答案 2 :(得分:5)

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

答案 3 :(得分:5)

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

正如其他人所说,使用自然语言,可能会有相对较少的唯一值,因此地图实际上不会那么大。我不会使用java.util.HashMap,因为它是very inefficient in terms of memory用法,尤其是在存储诸如int的原始值时。 java.util.HashMap将基元存储为对象。它还将每个值存储在浪费内存的HashMap.Entry对象中。由于这两个因素,java.util.HashMap比TroveFastutil等其他选项使用更多的内存:

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

答案 4 :(得分:4)

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

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

答案 5 :(得分:2)

您也可以尝试使用词干来增加重复次数。

例如, cat = Cats = cats = Cat

游泳=游泳=游泳

尝试Googling“Porter Stemmer”

答案 6 :(得分:1)

Trove THashMap使用的内存要少得多。不过,怀疑这是否足以减少规模。您需要在其他地方存储此信息以便在内存中进行检索。

答案 7 :(得分:1)

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

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 */ }
}

事实上,即使字符串不代表自然语言,这个解决方案也可能有效,前提是字符串之间可以利用很多重叠。

答案 8 :(得分:1)

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

答案 9 :(得分:0)

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

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

  2. 使用memcached。最简单的出路。内存分布式可清除。这正是DHT的用途。

答案 10 :(得分:0)

考虑将其替换为cdb。最高4 GB和:

  

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

答案 11 :(得分:0)

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

答案 12 :(得分:0)

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

我们可以使用由两个逻辑阵列支持的手动编码的hashmap。

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

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

  3. 我们可以根据键的长度说出这个哈希映射的10个实例;因为我不知道数据的特征,所以无法确定。

  4. 使用的空间= 2个电源29用于int数组+(2个电源4个(每个字符串16个字节)* 2个电源27)= 3.5个演出

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

答案 13 :(得分:0)

在java中,对象的开销至少为16字节 在考虑它所拥有的其他内容之前的大小。

哈希映射中的1e8项具有低估的大小要求 1e8 * 2 * 16字节,这是假设你的钥匙和 值是数字,因此需要几GB的可用堆 在你的堆中和你的计算机上。

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

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

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

答案 14 :(得分:-1)

由于失败的原因,我同意上述答案。

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

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

节点01的密钥以'a'开头 节点02的关键标记为'b'....

所以你的程序突然改为网络编程..