Java中的HashMap,1亿条目

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

任何帮助将非常感激。

谢谢 :-)

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

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

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

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

更新

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

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

正如其他人所说,使用自然语言,可能会有相对较少的唯一值,因此地图实际上不会那么大。 我不会使用java.util.HashMap,因为它在内存使用方面效率非常低,尤其是在存储诸如int的原始值时。 java.util.HashMap将基元存储为对象。 它还将每个值存储在浪费内存的HashMap.Entry对象中。 由于这两个因素,java.util.HashMap使用的内存远多于Trove , Fastutil等其他选项:

  • Java中的内存节省技术概述
  • 流行的Java数据类型的内存消耗 – 第2部分

如上所述,有几种地图实现没有这些问题。 由于您在地图中存储数字,所以额外的好处是您将获得性能提升,因为您不需要在对象和基元之间不断切换(即装箱/拆箱),因为您要在地图中添加新值或更新旧值值。 可以在Java性能调优指南的这篇文章中找到更适合大量数据的各种原始哈希图的基准:

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

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

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

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

例如,cat = Cats = cats = Cat

要么

游泳=游泳=游泳

尝试谷歌搜索“Porter Stemmer”

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

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

 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 

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

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

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

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

  2. 使用memcached 。 最简单的方法。 内存分布式可清除。 这正是DHT用于的目的。

考虑用cdb替换它。 最高4 GB和:

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

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

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

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

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

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

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

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

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

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

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

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

请注意,当您接近计算机的极限时,程序开始表现不佳。

如果您不想按照上面的建议使用数据库,可以考虑对密钥进行编码和压缩,使其成为可以计算频率的数字。 您可以根据第一次编码中的单词频率选择基于熵的编码,然后从那里开始…

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

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

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

节点01的密钥以’a’开头,节点02的密钥标注为’b’….

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