最小化内存的一种简单方法是逐行读取每个文件(“对于文件 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_VALUE 的 BitSet(感谢 @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。