内容定义分块算法
分块的目标是将文件数据转换为较小的可变长度块,长度约为 64 KiB。 块边界必须以确定性方式计算,以便在两个不同位置对相同数据进行分块时产生可以去重的块。
+---------+---------+---------+---------+---------+---------+---------+--------------
File -> | chunk 0 | chunk 1 | chunk 2 | chunk 3 | chunk 4 | chunk 5 | chunk 6 | chunk 7 | ...
+---------+---------+---------+---------+---------+---------+---------+--------------
逐步算法(基于 Gearhash 的 CDC)
常量参数
- target_chunk_size:
64 KiB - MIN_CHUNK_SIZE:
8 KiB(最小块大小) - MAX_CHUNK_SIZE:
128 KiB(最大块大小) - MASK:
0xFFFF000000000000(16 个一位 → 每字节边界概率 1/2^16) - TABLE[256]: 256 个 64 位常量表(rust-gearhash-table)
状态
- h: 64 位哈希,初始化为 0
- start_offset: 当前块的起始偏移量,初始化为 0
每字节更新规则(Gearhash)
对于每个输入字节 b,使用 64 位包装算术更新哈希:
h = (h << 1) + TABLE[b]
边界测试和大小约束
在更新 h 后的每个位置,令 size = current_offset - start_offset + 1。
- 如果
size < MIN_CHUNK_SIZE:跳过测试MASK;继续 - 否则如果
size >= MAX_CHUNK_SIZE:强制边界 - 否则如果
(h & MASK) == 0:此位置为边界
当找到或采用边界时:
- 发出块
[start_offset, current_offset + 1) - 设置
start_offset = current_offset + 1 - 重置
h = 0
在文件末尾,如果 start_offset < len(data),发出最终块 [start_offset, len(data))。
伪代码
输入: (参见上面的常量参数)
data: 字节数组
状态:
h = 0
start_offset = 0 // "当前块"的起始
if len(data) < MIN_CHUNK_SIZE:
发出块 [0, len(data))
完成
for i in range(0, len(data)):
b = data[i]
h = (h << 1) + TABLE[b] // 64 位包装
size = i + 1 - start_offset
if size < MIN_CHUNK_SIZE:
continue
if size >= MAX_CHUNK_SIZE or (h & MASK) == 0:
发出块 [start_offset, i + 1)
start_offset = i + 1
h = 0
if start_offset < len(data):
发出块 [start_offset, len(data))
边界概率和掩码选择
给定 MASK 有 16 个一位,对于随机 64 位哈希 h,所有这些 16 位都为零的概率是 1 / 2^16。平均而言,这意味着大约每 64 KiB 你会看到一次匹配。
属性
- 确定性边界:相同内容 → 相同块
- 局部性:小编辑仅影响附近边界
- 线性时间和恒定内存:单个 64 位状态和计数器