【问题标题】:How to use minimum memory when analyzing data from a huge file分析大文件中的数据时如何使用最少的内存
【发布时间】:2016-10-31 12:02:33
【问题描述】:

我有两个大文件,每个文件的每一行都会存储整数值。格式如下:

文件1:

3
4
11
30
0
...

文件2:

13
43
11
40
9
...

我需要找到一种方法来读取这两个文件并找到这两个文件上的重复值。让我们考虑上面的例子,值 11 将被打印,因为它出现在两个文件上。 从文件中读取和循环值很容易。但问题是每个文件的行数远远超过Integer.MAXIMUM。所以我不能将整个文件读入内存,否则我会用完内存。有没有什么有效的方法可以解决这个问题并且消耗更少的内存?

EDIT1

我想找到一个不将所有数据读入内存的解决方案。最好阅读文件的一部分并进行分析,然后继续阅读。但我不知道如何实现我的目标。

【问题讨论】:

  • But the problem is that the number of lines for each file is far more than Integer.MAXIMUM ... 那么为什么不使用Long 的集合呢?
  • 这也是个问题。即使使用Long,我仍然会耗尽内存。我想在不将所有数据读取到内存的情况下找到解决问题的方法。
  • 有数据限制吗?例如,文件中的数字在 [0,N] 范围内?
  • 不,没有数据限制。它可以是任何值。
  • 那么您将不得不将文件拆分为固定数据范围的有序子集。

标签: java algorithm


【解决方案1】:

最小化内存的一种简单方法是逐行读取每个文件(“对于文件 1 的每一行,对文件 2 的每一行进行比较行”),但可能需要很长时间,因为瓶颈是磁盘 I /O。它位于频谱的低端 - 最小内存,最长持续时间。 另一方面是创建巨大的堆并完全在内存上操作 - 最大内存,最短持续时间。 因此,最好的解决方案是平衡权衡,这通常需要更多的前期思考。

分析输入数据

如果两个文件仅包含整数(32 位)值,并且两个文件都包含 >2^32 个条目,那么您肯定会在第一个文件中已经存在重复项。

但要确定,如果文件 2 包含重复项,我们只需要知道相同的值是否在第一个文件中出现至少一次。我们必须保存在内存中的只是 1 位 信息,而不是整个值。 因为值范围被限制为整数,并且对于每个可能值,我们必须知道它是否至少出现一次,我们需要一个 2^32 位的位集。

在单个长值中,我们可以存储文件 64 位的 64 个值的信息)。所以我们需要一个大小为 67'108'864 的长数组来将文件 1 的整个最小出现信息存储在内存中。这大约是您需要的 512 MB 内存。

在您将文件 1 的这个表示读入内存之后。您可以逐行扫描文件 2 并检查每个值,如果它使用我们创建的数组在文件 1 中至少出现一次,并打印出它是否重复(或将其写入另一个文件或数据结构)。

智能版

如果您想弄脏自己的手,请继续阅读。如果您想使用 JDK 自带的功能,请使用大小为 Integer.MAX_VALUEBitSet(感谢 @Mzf)。

    BitSet bs = new BitSet(Integer.MAX_VALUE);
    bs.set(valueFromFile1); 
    boolean duplicate = bs.get(valueFromFile2); 

以 root 身份运行的留胡子男士的版本:

查找数组的结构是这样的

[ 0000 0001 0000 0000 ... , 0000 0000 1000 000 ..., ...]
          ^                           ^
          7 (0*64 + 7)                74 (1*64 + 8)

你需要的是从 int 值到索引位置和位偏移的转换。

int pos(int value){
    return value / 64;
}
long offsetMask(int value){
    return 1L << (value % 64)
}
boolean exists(long[] index, int value) {
    return (index[pos(value)] & offsetMask(value)) != 0;
}


long[] index = new long[67108864];

//read references file
Scanner sc = new Scanner(new File("file1"));
while (sc.hasNextInt()) {
    int value = sc.nextInt();
    index[pos(value)]  |= offsetMask(value);
}

//find duplicates
Scanner sc = new Scanner(new File("file2"));
while (sc.hasNextInt()) {
    int value = sc.nextInt();
    boolean result = exists(index, value)
    if(result) {
      System.out.println("Duplicate: " + value);
    }
}

(基本上和BitSet里做的一样)

文件是否更大并不重要,只要值范围不增加,您不需要超过 512 MB。

【讨论】:

【解决方案2】:

首先将每个文件从小到大排序。现在从每个文件中读取第一行。比较。如果它们匹配,则记录一个副本,然后转到下一个文件(我选择文件 A)。如果它们不匹配,请转到文件中具有较小行的下一行。重复直到到达一个文件的末尾。

【讨论】:

  • 如何实现排序,正如赵所说的“不会将所有数据读入内存”。排序后为什么不用二分查找?
  • 文件排序也需要加载到内存中对吧?
  • 如果你不能事先对文件进行排序,你将不得不接受二次复杂度。
  • @ZhaoYi 文件排序可以在不保存在内存中的情况下进行。 stackoverflow.com/questions/7918060/…
【解决方案3】:

首先,您必须将输入拆分为具有有序数据的较小文件。

这些文件应该有固定的数据范围,所以第一个文件的不同值在 0-1000000 的范围内,第二个文件的值在 1000001-2000000 的范围内,依此类推。

如果您对每个输入都执行此操作,您最终将得到给定范围内的有序值“桶”。您现在要做的就是将这些“桶”相互比较以获得重复值。

这会消耗磁盘空间以换取内存使用量。

乍一看,这就是我尝试解决这个问题的方法。

【讨论】:

    【解决方案4】:

    有几种方法可以解决这个问题。 但首先,您不必将整个文件读入内存,而是使用FileUtils 逐行读取 - 请参阅几个示例here

    你说行数大于 Integer.MAXIMUM。 因此,您可以逐行读取第一个文件,将不同的数字保存在 Hashmap 中。 现在逐行读取第二个文件,为每一行搜索地图中的数字 - 如果它存在 - 打印它。 使用的最大内存量是 Integer.MAXIMUM

    如果你想减少内存占用,你可以使用位图而不是地图,所以你将使用 Integer.MAXIMUM bits

    如果您对输入有所了解 - 您可以选择适合您的解决方案

    在这两种情况下,使用的内存都是有限的,不会将整个文件加载到内存中

    【讨论】:

    • 不同值的数量大于'Integer.MAXIMUM'
    • @Palcente - 这不是问题But the problem is that the number of lines for each file is far more than Integer.MAXIMUM 中所写的内容,而不是不同值的数量 - 或者我错过了什么?
    • @Palcente 不同值的数量可能如何大于 Integer.MAX_VALUE?仅存在 Integer.MAX_VALUE 个整数! (负面的除外……)
    • @KlitosKyriaco 这是我在这里读到的一些严重的 32 位异端邪说
    猜你喜欢
    • 2017-01-25
    • 2020-02-03
    • 2020-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-16
    • 1970-01-01
    • 2014-10-31
    相关资源
    最近更新 更多