【问题标题】:Fast way to check if a large set of file pairs are identical检查大量文件对是否相同的快速方法
【发布时间】:2015-09-10 06:17:00
【问题描述】:

我正在用 C++ 构建一个小型同步实用程序,主要供个人使用。

假设我们有两个将被同步的目录“A”和“B”。在某些时候,必须将 A 中的新文件复制到 B。我目前使用的逻辑是:

browse directory 'A'
for each 'A/afile'
    copy A/afile to B/afile
endfor
for each 'A/adirectory'
    recurse into 'A/adirectory'
endfor

这很好,直到我注意到使用上面的方法,A 中的所有文件每次都复制到 B。所以,如果 A/afile 和 B/afile 不同,我只想执行复制操作.

所以,我的问题是,如何以快速和跨平台(希望如此)的方式比较它们?为每个文件计算 MD5 校验和之类的东西会很快吗?

关键是,由于可能会针对大量文件对进行文件比较,所以我想要既可靠又快速的东西。我所说的快速是指“繁重且耗时”的任务应该是实际的复制操作,而不是文件检查。

PS。我还尝试寻找“技巧”,例如比较文件大小和修改时间,但没有成功。


编辑

在考虑了下面的答案后,我最终会检查两个文件是否相同:

if optimize_speed then
      if A/afile is newer then no (cause A/afile is the 'source' file)
      if B/afile is newer then compare byte-to-byte and decide 
else
      compare byte-to-byte and decide
end

【问题讨论】:

  • 要么使用 Qt 之类的框架(或任何其他具有文件系统模块的框架),要么使用系统 API。比较修改时间,不要比较大小,因为你可能会更改 txt 文件中的某些行,它的大小不会改变。
  • 几乎所有文件系统都有修改时间戳。您可以使用它来检查A中的文件是否在B中的相应文件之后被修改。
  • 问题是,如果我将A/afile复制到B/afile,那么'B/afile'的修改时间将是复制操作的时间,而不是A的修改时间/一份文件。但是,作为通用解决方案,该程序提供了两种同步模式:“镜像”和“附加”:附加已经可以正常工作,因为我只复制较新的文件。然而,镜像模式应该丢弃在 B/afile 中所做的任何更改,因此修改检查是不够的,因为 B/afile 可能在 A/afile 之后被更改。这就是为什么我认为文件比较是要走的路,除非.......
  • 除非有更简单、更清洁、更快捷的方法。另外,我想避免像 Qt 这样的小任务那样依赖。如果有帮助,您可以在此处找到源代码(查找函数:copy_all_files()、copy_new_and_updated_files()、doMirrorSync()、doAppendSync())。 github.com/neoaggelos/synctool
  • 您可能想看看 Linux cmp 命令行工具的源代码。它经过了很好的优化,运行 cmp --silent file1 file2 具有您所追求的确切语义。

标签: c++ file compare


【解决方案1】:

给定任意一对可同步文件A 和B,只要两个文件的修改时间戳不相等,就需要同步。

问题是咳咳……时间戳不是 C++ 标准的一部分……因此,您要么需要使用 Boost/Qt 之类的东西来实现跨平台目的。

当然,另一种方法是忽略可移植性并采用 POSIX 的解决方案(p.d:记得检查返回值!):

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utime.h>

struct stat statOfA;
struct stat statOfB;
stat(pathOfA, &statOfA);
stat(pathOfB, &statOfB);

if(statOfA.st_mtime > statOfB.st_mtime) {
    // Sync! Then...
    struct timeval now;
    gettimeofday(&now, NULL);    // nullptr is prefered in C++11...

    struct timeval copys[] = { now, now };
    utimes(pathOfA, copys);
    utimes(pathOfB, copys);
}

编辑:如果您需要使用 Windows API,您可能会看到 GetSystemTime()、SystemTimeToFileTime()、GetFileTime() 和 SetFileTime()。

【讨论】:

  • @Melebius:这只是如何的一个例子,它不是直接可编译的代码,但无论如何......(编辑)。
  • 何时触发同步例程与“检查文件对是否相同”不同。第二个可以是第一个的连锁事件,但不一定。无论哪种方式,这都不是问题的答案。 -1 来自我。
  • @rbaleksandar;诶?但是...您是否阅读了标题而不是问题的正文? OP 明确表示他正在搜索同步,而不仅仅是随机文件的比较。
  • 是的,我阅读了问题的标题和正文。同步可以由不同的时间戳触发,但是之后您仍然必须验证是否确实存在值得同步的内容。文件的内容(按字节)也很重要。想象一下,您修改了一个文件(比如更改了一个字符串),然后撤消了更改但重新保存了它。修改时间戳发生变化,但内容相同。
  • 只是为了你的使用没关系@neoaggelos。重要的是,仅依赖时间戳是不可靠的。计算复杂性很重要,但数据本身的处理也很重要 - 无意义的文件复制是对资源的浪费。
【解决方案2】:

这将是速度和可靠性之间的权衡。您想先尝试 fastet 方法,然后再尝试更精确的方法。这是fdupes后面的算法:

  • 比较文件大小
  • => 如果不同,则采取行动(在您的情况下,复制)
  • 比较 MD5 签名
  • => 如果不同,复制
  • 逐字节比较
  • => 如果不同,复制
  • 其他什么都不做

准备这个答案时,我刚刚了解到 fdupes 现在添加了一个带有部分 MD5 的中间步骤:

http://en.wikipedia.org/wiki/Fdupes

【讨论】:

  • 逐字节比较并在不同字节上立即中止比计算任何类型的签名要快得多,只要两个文件都是本地文件。
【解决方案3】:

由于到目前为止,每个答案都存在一些错误信息,因此这是我的看法:

  1. 如果您真的想知道两个文件是否相同,您必须逐字节比较它们。校验和相同但文件不同的可能性非常、非常、非常小,但校验和很好。然而,计算校验和几乎肯定比直接比较文件内容要慢当两个文件都是本地文件时。 (rsync 不比较文件内容的原因是它用于同步远程文件。)

  2. 如果您触摸文件或以其他方式更改其时间戳而不更改其内容的可能性很小,则继续并仅比较时间戳。在极少数情况下,您会复制未更改的文件,但不必比较不必要的文件内容。

  3. 比较大小不是一个好主意,尤其是。如果您更改了文件的某些内容。

  4. 是的,比较文件内容意味着读取两个文件。有一些方法可以提高效率,但仍然需要与较小的文件大小大致呈线性关系的时间。如果您真的想这样做,请考虑使用现有的命令行工具,例如 cmp。

这是调用cmp的一种方法:

cmp --silent file1 file2

这将告诉您两个文件的内容是否相同(退出状态 0)或不同(退出状态 1),或者是否有问题,例如两个文件之一不存在(退出状态 2)。一个 bash 脚本,它接受两个参数,如果它们不同,则将第一个参数复制到第二个参数:

if cmp --silent "$1" "$2"
then
    :
else
    cp "$1" "$2"
fi

重要信息:在实施解决方案之前弄清楚您的用例是什么。

【讨论】:

    【解决方案4】:

    比较时间戳只是解决方案的一部分,但远非真正完整的解决方案。我强烈反对@KemyLand。

    想象以下情况:

    您有两个文件 F1 和 F2。两者具有相同的内容。假设修改时间戳是 14:00:00。然后您决定更改 F1 并保存它。 F1 的修改时间戳更改为 14:02:00。但是,在下一次同步事件之前,您在 F1 中反转了更改(例如,您已经删除了其中的一个字符串,但决定返回并再次添加该字符串)。 F1 的修改时间戳再次更改为 14:06:00。然而这里我们有一个问题 - 即使 F1 的修改时间戳是 14:06:00 并且 F2 - 14:00:00 - content 本身也是一样的!

    但是,时间戳差异可以触发对要同步的文件进行更深入的检查。然而,必须检查文件的内容,而散列是最好的方法。

    为大量文件对计算哈希可能会很痛苦。但是,您可以做的是扩展您的工具并尝试优化您拥有的资源的使用。例如,如果您有 2 个核心 - 使用两者。如果您有 8 个核心 - 使用全部 8 个,依此类推。

    如果您使用时间戳作为唯一检查,您最终可能会不必要地复制文件。计算一对文件的哈希值比仅仅因为时间戳改变但其内容保持不变而盲目复制文件堆要好得多。

    使用触发事件的组合进行更深入的检查(时间戳更改、文件大小更改等),但始终进行散列(或至少使用 cmp 之类的东西进行比较)以确保您不会陷入其中在许多情况下(我上面提到的只是冰山一角),您浪费了对数据的读写访问。与复制数据相比,为大量文件计算哈希值要高效得多。如果您有 HDD 或 SSD 作为存储,这无关紧要。在读写方面,您必须移动的文件越多,情况就越糟糕。每个文件的大小也很重要。

    【讨论】:

    • 同样,当两个文件都是本地文件时,散列 不是比较文件的最佳方法。直接比较内容,直到第一个不同。无论哪种方式,都有一个命令行工具cmp,它在以cmp --silent file1 file2 调用时正是如此。
    • 好吧...我应该说多少次? 与仅仅检查一个微小而优雅的时间戳相比,哈希太昂贵且​​难以预测(在O(n) 时间)。无论如何,您真的是否经常更改文件然后撤消自己的更改所以?
    • @KemyLand 我添加了一些东西,因为我对我写答案的方式不满意。此外,我必须阅读所有出现的 cmets 并查看我可以更改的内容,以便更清楚我想说什么。我想您也可以在 Internet 上查找内容,看看您是否可以改进您的答案。该死,昨天我在一年前给出的答案中添加了一些东西!
    • P.D:我建议进行编辑,因为我不确定(根据网站的规则)直接且不必要地提及我的用户名是否正确。这不是出于任何个人原因,因为我对此没有任何问题,而是为了避免给大家带来更多问题。
    • @KemyLand 你只是touch file 并且突然之间时间戳是不同的,即使文件(未知大小)没有改变。检查时间戳是解决方案的一部分,完整的解决方案取决于用例。
    猜你喜欢
    • 2023-03-16
    • 2018-01-03
    • 2012-03-01
    • 1970-01-01
    • 2019-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    相关资源
    最近更新 更多