在java中加载大型文本文件的最佳方法

时间:2014-10-02 07:44:47

标签: java string memory

我有一个文本文件,每行有一个整数序列:

47202 1457 51821 59788 
49330 98706 36031 16399 1465
...

该文件有300万行这种格式。我必须将此文件加载到内存中并从中提取5-gram并对其进行一些统计。我确实有内存限制(8GB RAM)。我试图最小化我创建的对象的数量(只有1个类,包含6个浮点变量,以及一些方法)。并且该文件的每一行基本上生成该类的对象数(与#ofwords中的行的大小成比例)。当C ++出现时,我开始觉得Java不是一个很好的方法来做这些事情。

编辑: 假设每一行产生该类的(n-1)个对象。其中n是由空格分隔的该行中的令牌数(即1457)。因此,考虑到每行10个字的平均大小,每条线平均映射到9个对象。所以,会有9 * 3 * 10 ^ 6个对象。所以,所需的内存是:9 * 3 * 10 ^ 6 *(8字节obj标题+ 6 x 4字节浮点数)+(一个map(字符串,对象)和另一个映射(Integer,ArrayList(Objects)))。我需要将所有内容保存在内存中,因为之后会发生一些数学优化。

2 个答案:

答案 0 :(得分:14)

阅读/解析文件

以任何语言处理大型文件的最佳方法是尝试 NOT 将它们加载到内存中。

在java中,看看MappedByteBuffer。它允许您将文件映射到进程内存并访问其内容,而无需将整个内容加载到堆中。

您也可以尝试逐行读取文件并在读取后丢弃每一行 - 再次避免将整个文件同时保存在内存中。

处理生成的对象

为了处理解析时产生的对象,有几个选项:

  1. 与文件本身相同 - 如果您可以执行任何想要执行的操作而不将所有内容保留在内存中(同时“流式传输”文件) - 这是最佳解决方案。你没有描述你试图解决的问题所以我不知道这是否可能。

  2. 压缩某些类型 - 从Wrapper对象(Float)切换到基元(float),使用the flyweight pattern之类的东西将数据存储在巨型float []数组中,只构造短期对象访问它,在您的数据中找到一些模式,允许您更紧凑地存储它

  3. 缓存/卸载 - 如果您的数据仍然不适合内存“将其分页”到磁盘。这可以像extending guava to page out to disk一样简单,也可以引入像ehcache这样的库等。

  4. 关于java集合和地图的说明

    对于小型对象,特别是java集合和映射会导致大量内存损失(主要是由于所有内容都被包装为对象以及Map.Entry内部类实例的存在)。如果内存消耗是一个问题,那么你应该看看gnu trove集合,而不是优雅的API。

答案 1 :(得分:0)

最佳只能保持整数和行结束。

为此,一种方法是:将文件转换为两个文件:

  • 一个整数的二进制文件(4个字节)
  • 一个带索引的二进制文件,下一行将开始。

为此,可以使用Scanner进行读取,并使用DataOutputStream + BufferedOutputStream进行写入。

然后你可以将这两个文件加载到基本类型的数组中:

int[] integers = new int[(int)integersFile.length() / 4];
int[] lineEnds = new int[(int)lineEndsFile.length() / 4];

可以使用MappedByteBuffer.toIntBuffer()完成阅读。 (你甚至不需要数组,但它会变得有点像COBOL那样冗长。)