重复数据删除有多种方法。必须知道文件的内部结构可能是最不吸引人的选择——至少对我来说是这样。我们做了类似于您要求的事情,并围绕它构建了一个产品。
几个观察;首先,您可能已经听够了,考虑到问题的年龄,MD5 不是您最好的朋友。在这些类型的应用程序中使用的碰撞概率太高。我们选择了 SHA-1,还有很多其他做类似工作的产品。
您已经认识到数据的简单“分块”的问题...在文件早期插入的情况下,所有后续块可能都必须重写。
首先,您可能会认识到,低于某个大小阈值,这无关紧要。更改后的较小文件的 IO 可能只是您吸收的内容。
但是,对于较大的文件,如果只有一小部分更改,则不必重写所有数据会很好......并且对于许多大型可写文件,大型静态数据集中的小更改正是发生的情况.例如,具有内部数据库结构的文件。
如果可以将其视为技巧,则技巧是识别静态数据范围。这相当于计算您识别的数据的哈希值。
例如,想象一下在您浏览文件时逐字节计算滚动哈希。如果您的散列函数具有合理的分布性,则每个新字节都会产生一个散列值,该散列值与前一个字节的贡献相当随机。
识别散列仅意味着散列值位于您任意选择的某个值的子集中...您已决定代表 sentinel 散列。例如,您可能会识别出除以一个常数值的所有哈希值。
当您识别出哈希时,您会捕获文件中该字节的偏移量,并将滚动哈希重置为其初始状态。在文件末尾,您将累积一个偏移量列表。
现在,这些偏移量之间的相对距离将由您对哈希识别器的选择性控制。例如,如果您选择识别hash % 32 == 0,那么您将在彼此之间相对较小的距离处有很多偏移量。如果您有hash % 65536 == 0,您将拥有更少、更宽的偏移量。每个偏移之间的距离是可变的……有些会很小,有些会很大。 注意:大块非常容易压缩。
这些偏移量将成为断点...您将存储从偏移量到偏移量的块。在存储块之前,您将计算该块的哈希(SHA-1 哈希,而不是运行哈希)。如果您已经在存储中获得了该块,则无需再次存储它。在您的存储中,文件成为块列表。块最初可能来自一个文件,但也会被识别为出现在其他文件中。去重!
您对运行哈希应用的选择性不仅控制块大小,还控制您在大文件中捕获“小变化”的能力。
在这一点上,区分 running 哈希和 rolling 哈希很重要。非常重要的是,当您在文件中滚动很长时间时,您正在计算的是 last n bytes 上的散列,其中 n 是滑动框架的宽度。我们不计算从一个偏移量到另一个偏移量的哈希值。我们正在尝试找到我们识别的 n 字节标记。
n 的大小也很重要。您将计算 0 到 n-1、1 到 n、2 到 n+1 等的哈希值。如果您考虑一下,n 表示将存在的最小块大小(除了紧跟在哨兵之后的文件结尾)。
所以,在这一点上,你必须在想,“天哪,这是很多散列!”,你是对的;但它并没有你想象的那么糟糕。选择正确的滚动哈希算法非常重要。有一种算法非常适合这种情况。我们使用的一种称为 Rabin-Karp 滚动哈希,它使用 Rabin fingerprint 来发现标记偏移量,它的美妙之处在于添加一个字节的贡献和删除一个字节的贡献是微不足道的、廉价的算法。
滚动哈希很重要(与运行哈希相反)的原因是更改检测。假设一个已识别的标记偏移发生在更改之前……然后另一个已识别的标记发生在更改之后。只有这两个偏移量之间的块将被存储。更改前的部分和更改后的部分将预先存储。