繁体   English   中英

Java-数百万条记录,HashMap抛出OutOfMemoryError

[英]Java - Millions of records, HashMap throws OutOfMemoryError

我正在读取一个文件,以将每个记录的几个字段解析为参考键,并将另一个字段解析为参考值。 这些键和值将用于另一个过程。 因此,我选择了一个HashMap,以便可以轻松获得每个键的值。

但是,每个文件都包含数千万或记录。 因此,HashMap抛出OutOfMemoryError。 我希望如果将来的输入文件增加,增加堆内存将不是一个好的解决方案。

对于SO中类似的问题,大多数人建议使用数据库。 我担心我不会被允许使用数据库。 还有其他解决方法吗?

编辑:我需要为4个这样的文件执行类似的HashMap加载:(我需要全部这四个。Bcoz,如果在第一个Map中找不到与我输入内容匹配的条目,则需要在第二个中查找,如果没有,然后是第三名,最后是第四名。

编辑2:我拥有的文件总计约为1 GB。 编辑3:

034560000010000001750                                  
000234500010000100752                            
012340000010000300374

我在文件中有这样的记录。.对于所有数百万条记录,我都需要03456000001000000作为键,并将1750作为值。 我将引用这些键并获取另一个过程的值。

使用数据库本身不会减少内存成本或运行时间。

但是,默认的哈希图可能不是您要查找的,具体取决于您的数据类型。 当与原始值(例如Integer一起使用时,java哈希表会占用大量内存。 HashMap<Integer, Integer> ,每个条目使用24 + 16 + 16个字节。 未使用的条目(哈希表最多保留其中一半未使用的)要多花4个字节。 因此,您可以大致估计Java HashMap<Integer, Integer>每个int-> int条目> 56个字节。

如果将整数编码为String ,并且我们正在谈论的可能是6位数字,则底层char[]数组可能为24个字节(16位字符;该数组为12个字节的开销,大小是8的倍数!) ,加上周围的String对象的16个字节(也可能是24个字节)。 对于关键和价值。 这样就大约是24 + 40 + 40,即每个条目超过104个字节。 (更新:由于您的密钥长度为17个字符,因此请使其为24 + 62 + 40,即136个字节)

如果您使用原始的哈希映射,例如GNU Trove TIntIntHashMap ,那么它将仅占用8个字节+未使用,因此让我们估计每个条目16个字节,至少少6倍的内存。 (更新:对于TLongIntHashMap ,每个条目估计12个字节,带有未使用存储区的开销估计为24个字节。)

现在,您还可以将所有内容存储在一个庞大的排序列表中。 这将使您能够执行快速联接操作,并且将浪费大量未使用条目的开销,并且可能会在更短的时间内处理两倍。

哦, 如果您知道有效值范围,则可以将数组滥用为“ hashmap”

也就是说,如果您的有效键是0 ... 999999,则只需使用int[1000000]作为存储,然后将每个条目写入相应的行。 根本不存储键-它是数组中的偏移量。

最后但并非最不重要的一点是,默认情况下,Java仅使用25%的内存。 您可能想增加其内存限制。

简短的回答:不。 很显然,您无法将整个数据集加载到内存中。 您需要一种将其与索引一起保存在磁盘上的方法,以便您可以访问数据集的相关位,而无需在每次请求新密钥时都重新扫描整个文件。

本质上,DBMS是一种用于处理(大量)数据的机制:存储,检索,合并,过滤等。它们还为常用的查询和响应提供缓存。 因此,您要做的就是(部分)重新实现DBMS已经完成的工作。

我了解您对依赖外部组件的担心,但是请注意,DBMS不一定是服务器守护程序。 有一些很小的DBMS可以与您的程序链接,并将所有数据集保存在一个文件中,就像SQLite一样。

如此大的数据收集应通过数据库进行处理。 Java程序的内存有限,因设备而异。 您没有提供有关程序的信息,但请记住,如果该程序在不同的设备上运行,则其中一些内存可能很小,并且崩溃很快。 对于大数据程序,必须使用DB(无论是SQL还是基于文件的数据库)。

你必须

a)具有足够的内存负载以将数据加载到内存中。

b)必须从磁盘读取数据,索引必须在内存中或不在内存中。

无论您是否使用数据库,问题都差不多。 如果没有足够的内存,则开始随机访问磁盘时,性能会急剧下降。

诸如Chronicle Map之类的替代方案使用了堆,并且执行得很好,可以使您的主内存大小增加一倍,因此不会出现内存不足错误,但是仍然存在无法在内存中存储更多数据的问题主内存。

内存占用量取决于您如何使用Java处理文件。 广泛使用的解决方案基于使用Apache Commons IO LineIterator传输文件的方式 他们的推荐用法

 LineIterator it = FileUtils.lineIterator(file, "UTF-8");
 try {
   while (it.hasNext()) {
     String line = it.nextLine();
     // do something with line
   }
 } finally {
   it.close();
 }

这是一种优化的方法,但是如果文件太大,仍然可以使用OutOfMemory

由于您写道,您担心自己不会获得使用数据库的选项,因此某种嵌入式DB可能是答案。 如果不可能将所有内容都保留在内存中,则必须将其存储在其他位置。

我相信使用磁盘作为存储的某种嵌入式数据库可能会起作用。 示例包括BerkeleyDBNeo4j 由于两个数据库都使用文件索引进行快速查找,因此与将整个负载都保留在内存中相比,内存负载要小一些,但是它们仍然很快。

您可以尝试延迟加载它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM