Files
kaka111222333-kaka111222333…/_posts/2018-03-26-large-file-diff.md
2019-11-17 01:12:14 +08:00

4.1 KiB
Raw Blame History

title, tags, categories
title tags categories
大文件内容对比 java 算法 algorithm 排序 algorithm
  • TOC {:toc}

最近接到一个需求要对两个系统的订单进行对比找出其中的差异对比结果有4种一致、不一致、丢失、多余。

如果数据量少处理起来就很简单例如要对A,B两份数据进行对比

  1. 将数据A放入哈希表
  2. 遍历数据B依据id从A中查找对应的订单
  3. 若从A中找到了对应的订单则比较是否一致并将此订单标记为已匹配
  4. 若从A中找不到对应的订单则表示A丢失此订单
  5. 遍历A筛选出所有未匹配的订单这些订单都是A中多余的

但是如果数据量超大,在内存中处理就不合适了,数据需要放在磁盘上。 基于哈希的对比算法无法避免大量对磁盘的随机访问因而无法使用buffer完全的io读写性能太差所以这个方案是不行的。

如果AB两个集合是有序的那么对比将会变得容易一次遍历就可以对比完成例如以id排序

  1. 读取A指针对应的订单读取B指针对应的订单
  2. 若两个订单id相等则对比内容是否一致
  3. 若A指针订单id>B指针订单id代表A中缺少B指针订单记录结果B指针移动到后一个
  4. 若A指针订单id<B指针订单id代表A中多余了指针订单记录结果A指针移动到后一个

所以这个问题可以拆分为对A、B文件内容进行排序双指针遍历 A、B。

大文件内容排序

对大文件的内容进行排序,可以使用拆分->排序->合并的思路

  1. 按行读取文件将读到的行放入list
  2. 如果已读取的行达到了1万行对list进行排序将排序后的list逐行写入临时文件
  3. 打开多个临时文件从每个文件的中逐行读取选取其中订单id最小的行写入新的临时文件
  4. 循环以上步骤,直至剩下一个文件

示例代码

while (true){
    line = br.readLine();
    if(line != null){
        chunkRows.add(line);
    }
    if(line == null || chunkRows.size() >= initialChunkSize){
        if(chunkRows.size() > 0){
            chunkRows.sort(comparator);
            Chunk chunk = initialChunk(rowNum, chunkRows, file);
            chunkList.add(chunk);
            rowNum += chunkRows.size();
            chunkRows.clear();
        }
    }
    if(line == null){
        break;
    }
}
while (true){
    List<Chunk> pollChunks =  pollChunks(chunkQueue, currentLevel);
    if(CollectionUtils.isEmpty(pollChunks)){
        currentLevel++;
        continue;
    }
    //合并
    Chunk mergedChunk = merge(pollChunks, original);
    //chunkQueue 中没有后续的元素,表示此次合并是最终合并
    if(chunkQueue.size() == 0){
        chunkQueue.add(mergedChunk);
        break;
    } else {
        chunkQueue.add(mergedChunk);
    }
}

完整代码

测试

在最后的多路合并中要考虑使用多少路进行合并,更少的路数,将产生更多的临时文件

拆分时需要考虑每个块的大小过大的块将造成频繁的FULL GC

IO的缓冲容量区也要考虑这将影响IO读写的速度以及FULL GC的频率

以如下配置为例,在我的个人电脑上, cpu频率3.40GHZ磁盘顺序读450MB/s随机读190MB/s顺序写250MB/s数据宽度20为20字符 对1千万条数据进行排序耗时13秒。

FileSorter sorter = new FileSorter((p,n) -> p.compareTo(n),
    8, 50000, 1024*1024*2, new File("/var/tmp/fileSorter"), false);
long start = System.currentTimeMillis();
sorter.sort(file, dest);
long end = System.currentTimeMillis();
System.out.println(end - start);

该排序算法既有CPU计算又有IOCPU计算时IO空闲IO时CPU空间如果把该方法改写成多线程版本理论上可以提升不少速度。