bigywater.tech
Java · 399

Files.mismatch:高效比较两个文件的第一个差异

比较两个文件有很多写法:读进内存、算摘要、自己写循环,或者 Files.mismatch。这篇用 128 MiB 的真实文件量出它们的耗时与内存分配,以及 Files.mismatch 在边界情况下的返回值。

0.1 ms vs 41 ms
Files.mismatch 在差异位于开头与完全相同的两个 128 MiB 文件上的耗时
0 vs 213 MiB
Files.mismatch 与 readAllBytes × 2 每次调用的线程分配量
40.1 ms vs 0.007 ms
前缀相同、大小差 1 字节的两个文件,Files.mismatch 与先比较 Files.size 的耗时

返回值与边界

实测 · 本机执行输出
  内容相同的两个文件:-1
  "hello world" vs "hello_world"(第 5 个字节不同,下标从 0 起):5
  "hello world" vs "hello"(后者是前者的前缀):5(= 较短文件的长度 5)
  参数顺序对调:5、5
  空文件 vs 非空文件:0
  同一个路径与自己比较:-1
  符号链接与它指向的文件:-1
  其中一个文件不存在:NoSuchFileException:<dir>/nope
  文件与目录:IOException:Is a directory
  空文件与 /dev/null:-1

差异位置与耗时

实测 · 本机执行输出
  生成 A 与 5 个变体,每个 128 MiB
  B_start   Files.mismatch = 0          中位数 0.1 ms;需要比较的字节数 0 MiB(两个文件各一份)
  B_mid     Files.mismatch = 67108864   中位数 20.3 ms;需要比较的字节数 64 MiB(两个文件各一份)
  B_end     Files.mismatch = 134217727  中位数 40.9 ms;需要比较的字节数 127 MiB(两个文件各一份)
  B_same    Files.mismatch = -1         中位数 41.2 ms;需要比较的字节数 128 MiB(两个文件各一份)
  B_longer  Files.mismatch = 134217728  中位数 39.3 ms;需要比较的字节数 128 MiB(两个文件各一份)

几种比较写法

实测 · 本机执行输出
  [B_same]
      Files.mismatch                     中位数    38.8 ms;每次调用线程分配约 0.0 MiB
      readAllBytes × 2 + Arrays.equals   中位数    65.3 ms;每次调用线程分配约 213.3 MiB
      SHA-256 两个文件再比较摘要                  中位数   138.7 ms;每次调用线程分配约 1.7 MiB
      自写循环 8 KiB 缓冲                      中位数    37.1 ms;每次调用线程分配约 0.0 MiB
      自写循环 64 KiB 缓冲                     中位数    25.5 ms;每次调用线程分配约 0.1 MiB
      自写循环 1 MiB 缓冲                      中位数    24.7 ms;每次调用线程分配约 1.7 MiB
  [B_start]
      Files.mismatch                     中位数     0.0 ms;每次调用线程分配约 0.0 MiB
      readAllBytes × 2 + Arrays.equals   中位数    58.4 ms;每次调用线程分配约 213.3 MiB
      SHA-256 两个文件再比较摘要                  中位数   133.3 ms;每次调用线程分配约 1.7 MiB
      自写循环 8 KiB 缓冲                      中位数     0.0 ms;每次调用线程分配约 0.0 MiB
      自写循环 64 KiB 缓冲                     中位数     0.1 ms;每次调用线程分配约 0.1 MiB
      自写循环 1 MiB 缓冲                      中位数     0.3 ms;每次调用线程分配约 1.7 MiB

前缀相同而大小不同

实测 · 本机执行输出
  A 与 B_longer 的大小:134217728 / 134217729(B 只比 A 多 1 字节,前缀相同)
  Files.mismatch(A, B) 返回 134217728,中位数 40.1 ms;先比 Files.size 再决定要不要 mismatch(大小不同就认为不相同,返回 0 的占位):中位数 0.007 ms
  结论:Files.mismatch 返回的是第一个差异的位置,所以大小不同、前缀相同的两个文件它也要读完较短的那个;只想知道“是否相同”时,先比较 Files.size 可以省掉这次读取

分步演示

Files.mismatch
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;文件在系统页缓存里(没有清缓存),所以数字是内存带宽级别而不是磁盘速度;随机数据由 java.util.Random(1) 生成
    • 没有验证:冷缓存(磁盘读)下的结果、网络文件系统、超大文件(几十 GB)与内存映射比较(FileChannel.map)、符号链接循环、Windows 上的行为
    京ICP备2026067502号-1京公网安备11010502063487号