跳到主要内容

GeneralUpdate.Differential

命名空间: GeneralUpdate.Differential | 主要入口: IBinaryDifferBsdiffDifferStreamingHdiffDiffer | NuGet 包: GeneralUpdate.Differential

1. 组件简介

1.1 组件概述

GeneralUpdate.Differential 是 GeneralUpdate 的二进制差分组件,专注解决"一个旧文件 + 一个补丁文件 = 一个新文件"的问题。它提供可替换的文件级差分算法(BSDIFF 4.0 / Streaming HDiff)、补丁压缩抽象(BZip2 / Deflate,源码中预留 .NET 6+ Brotli)和 BSDIFF 兼容补丁读写能力。

目录级对比、批量补丁生成、并行调度、删除文件处理和更新流程编排由 GeneralUpdate.CoreDiffPipelineGeneralUpdate.Tools 承担。

核心能力:

能力说明
文件级差分生成CleanAsync(oldFile, newFile, patchFile) — 对比新旧文件生成 .patch 补丁
文件级差分应用DirtyAsync(oldFile, newFile, patchFile) — 旧文件 + 补丁 → 新文件
可替换差分算法BsdiffDiffer(BSDIFF 4.0,后缀排序)和 StreamingHdiffDiffer(块哈希索引)
可替换压缩格式BZip2 (0x00)、Deflate (0x01),源码中通过 #if NET6_0_OR_GREATER 条件编译预留 Brotli (0x02)
BSDIFF 兼容格式写入 33 字节扩展头(32 字节 BSDIFF40 + 1 字节压缩格式),兼容 32 字节旧头
线程安全内置 differ 和压缩提供器均支持并发调用

解决的业务痛点:

  • 全量更新带宽成本高,差分更新可将更新包从 GB 级降低到 MB 甚至 KB 级
  • 不同文件类型和变化模式需要不同的差分策略(细粒度匹配 vs 快速块匹配)
  • 压缩算法的选择影响客户端解压速度和补丁体积的平衡
Differential 是底层库,通常不直接使用

大多数场景下你不需要直接调用 IBinaryDiffer。目录级差分、批量补丁生成、并行调度和更新流程编排由 GeneralUpdate.CoreDiffPipelineGeneralUpdate.Tools 承担。Differential 只解决一个原子问题:一个旧文件 + 一个补丁文件 = 一个新文件

业务使用场景:

  • 大型桌面应用(多 DLL、资源文件)的增量更新
  • 固件/驱动包的二进制差分分发
  • 游戏资源热更新
  • CI/CD 发布流水线中自动生成增量补丁包

1.2 环境与依赖

项目说明
版本10.5.0-beta.2
目标框架netstandard2.0(兼容 .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+)
依赖包无外部依赖(纯 .NET BCL)
兼容性所有支持 .NET Standard 2.0 的平台

2. 组件功能列表

功能名称功能描述类型是否必填备注限制
BSDIFF 4.0 差分生成基于后缀排序的经典差分算法,补丁体积稳定基础可选BsdiffDiffer,默认 BZip2 压缩
BSDIFF 4.0 补丁应用将 BSDIFF 格式补丁应用到旧文件基础可选支持 32/33 字节两种头部格式
Streaming HDiff 差分生成基于 FNV-1a 块哈希索引的快速差分基础可选StreamingHdiffDiffer,默认 Deflate 压缩
BZip2 压缩补丁控制段/差异段/额外段的 BZip2 压缩基础可选格式字节 0x00BsdiffDiffer 默认
Deflate 压缩补丁段的 Deflate 压缩,解压更快基础可选格式字节 0x01StreamingHdiffDiffer 默认
自定义差分算法实现 IBinaryDiffer 接入自研算法拓展可选需保证 Clean/Dirty 一致性
自定义压缩提供器实现 ICompressionProvider 替换压缩方式拓展可选新格式字节需配合扩展补丁读取逻辑

3. API 配置说明

3.1 配置字段(属性 Props)

Differential 本身是底层库,不提供配置类。所有参数通过构造函数传入。

BsdiffDiffer 构造参数:

字段名数据类型默认值是否必填枚举/取值范围说明
compressionProviderICompressionProviderBZip2CompressionProvider可选BZip2CompressionProvider / DeflateCompressionProvider补丁压缩提供器

StreamingHdiffDiffer 构造参数:

字段名数据类型默认值是否必填枚举/取值范围说明
compressionProviderICompressionProviderDeflateCompressionProvider可选BZip2CompressionProvider / DeflateCompressionProvider补丁压缩提供器
blockSizeint65536(64 KB)可选正整数字节数块大小,用于旧文件哈希索引
maxWindowSizeint134217728(128 MB)可选正整数字节数参与计算的最大内存窗口

DeflateCompressionProvider 构造参数:

字段名数据类型默认值是否必填枚举/取值范围说明
optimalLevelbooltrue可选true / falsetrue = CompressionLevel.Optimalfalse = CompressionLevel.Fastest

ICompressionProvider 格式标识:

Provider格式字节可用性说明
BZip2CompressionProvider0x00完全可用BSDIFF 旧补丁兼容,解压成本较高
DeflateCompressionProvider0x01完全可用解压速度更友好,适合客户端批量应用
BrotliCompressionProvider0x02仅 .NET 6+ 编译(源码中为完整实现,通过 #if NET6_0_OR_GREATER 条件编译)当前 netstandard2.0 包中不包含,生产不建议使用

3.2 实例方法

IBinaryDiffer:

方法名入参明细使用场景注意事项
CleanAsync(string, string, string, CancellationToken)oldFilePath — 旧文件路径;newFilePath — 新文件路径;patchFilePath — 补丁输出路径;cancellationToken发布/构建阶段生成补丁大文件取消不会立即响应,需等待当前文件处理完成
DirtyAsync(string, string, string, CancellationToken)oldFilePath — 旧文件路径;newFilePath — 补丁还原后文件输出路径;patchFilePath — 补丁文件路径;cancellationToken客户端升级阶段应用补丁不会直接覆盖旧文件,结果写入 newFilePath

3.3 回调事件

Differential 不发布事件。进度报告和事件通知由 Core 的 DiffPipeline 通过 DiffProgressEventManager 实现。


4. 扩展示例(高阶用法)

4.1 组件可扩展能力总览

扩展接口说明
IBinaryDiffer自定义文件级差分算法,可接入原生库或自研算法
ICompressionProvider自定义补丁段压缩方式

4.2 分场景示例

场景 1:自定义差分算法

【场景说明】接入企业内部自研的高压缩率差分算法。

【示例代码】

using GeneralUpdate.Differential.Abstractions;

public sealed class HighRatioDiffer : IBinaryDiffer
{
public Task CleanAsync(
string oldFilePath,
string newFilePath,
string patchFilePath,
CancellationToken cancellationToken = default)
{
// 调用自研算法生成补丁
// NativeMethods.GeneratePatch(oldFilePath, newFilePath, patchFilePath);
return Task.CompletedTask;
}

public Task DirtyAsync(
string oldFilePath,
string newFilePath,
string patchFilePath,
CancellationToken cancellationToken = default)
{
// 调用自研算法应用补丁
// NativeMethods.ApplyPatch(oldFilePath, patchFilePath, newFilePath);
return Task.CompletedTask;
}
}

// 在 Core DiffPipeline 中使用
using GeneralUpdate.Core.Pipeline;

var pipeline = new DiffPipelineBuilder()
.UseDiffer(new HighRatioDiffer())
.WithParallelism(4)
.Build();

await pipeline.CleanAsync(oldDir, newDir, patchDir);

【效果&注意事项】

  • 必须保证 CleanAsync 产出的补丁能被同一算法的 DirtyAsync 正确应用
  • 发布侧和客户端必须使用同一套 differ 实现

场景 2:自定义压缩提供器 + BsdiffDiffer

【场景说明】使用 BsdiffDiffer 算法 + Deflate 压缩,获得更快的客户端补丁应用速度。

【示例代码】

using GeneralUpdate.Differential.Abstractions;
using GeneralUpdate.Differential.Differ;

// BsdiffDiffer 的精确匹配 + Deflate 的快速解压
var differ = new BsdiffDiffer(
new DeflateCompressionProvider(optimalLevel: false));

await differ.CleanAsync(oldFile, newFile, patchFile);
await differ.DirtyAsync(oldFile, outputFile, patchFile);

【效果&注意事项】

  • optimalLevel: false 生成更快,适合开发/CI 环境
  • optimalLevel: true 补丁体积更小,适合生产环境
  • 生成和消费两侧都需要能识别 Deflate 格式(0x01

场景 3:StreamingHdiffDiffer 参数调优

【场景说明】大型单文件(200MB+)的差分,调整窗口预算避免内存溢出。

【示例代码】

using GeneralUpdate.Differential.Abstractions;
using GeneralUpdate.Differential.Differ;

// 大文件场景:增大窗口,减小块大小以获得更精细匹配
var differ = new StreamingHdiffDiffer(
compressionProvider: new DeflateCompressionProvider(optimalLevel: true),
blockSize: 32 * 1024, // 32 KB 块,更密集的哈希索引
maxWindowSize: 256 * 1024 * 1024); // 256 MB,允许读入更大文件

await differ.CleanAsync(oldLargeFile, newLargeFile, patchFile);

【效果&注意事项】

  • blockSize 越小,哈希索引越密集,匹配更精确但内存消耗更大
  • maxWindowSize 决定能参与计算的最大数据量,超出部分不参与匹配
  • 超大文件建议先在业务侧压测补丁体积和应用还原结果

5. 常规使用示例

5.1 快速入门示例(最简 demo)

using GeneralUpdate.Differential.Abstractions;
using GeneralUpdate.Differential.Differ;

IBinaryDiffer differ = new BsdiffDiffer();

var oldFile = @"D:\releases\1.0.0\app.dll";
var newFile = @"D:\releases\1.0.1\app.dll";
var patchFile = @"D:\patches\app.dll.patch";
var outputFile = @"D:\restore\app.dll";

// 生成补丁:oldFile + newFile → patchFile
await differ.CleanAsync(oldFile, newFile, patchFile);

// 应用补丁:oldFile + patchFile → outputFile
await differ.DirtyAsync(oldFile, outputFile, patchFile);

// 验证还原结果
var newHash = ComputeSha256(newFile);
var outputHash = ComputeSha256(outputFile);
Console.WriteLine(newHash == outputHash ? "Patch verified." : "MISMATCH!");

5.2 基础参数组合示例

using GeneralUpdate.Differential.Abstractions;
using GeneralUpdate.Differential.Differ;

// 方案 A:经典 BSDIFF + BZip2 → 补丁体积最小
var differA = new BsdiffDiffer();

// 方案 B:经典 BSDIFF + Deflate → 补丁体积小 + 应用更快
var differB = new BsdiffDiffer(new DeflateCompressionProvider(optimalLevel: true));

// 方案 C:Streaming HDiff + Deflate → 生成快 + 应用最快
var differC = new StreamingHdiffDiffer(
new DeflateCompressionProvider(optimalLevel: true),
blockSize: 64 * 1024,
maxWindowSize: 128 * 1024 * 1024);

// 对同一组文件测试三种方案,选择最优
foreach (var differ in new IBinaryDiffer[] { differA, differB, differC })
{
var sw = Stopwatch.StartNew();
await differ.CleanAsync(oldFile, newFile, patchFile);
sw.Stop();

var patchSize = new FileInfo(patchFile).Length;
Console.WriteLine($"{differ.GetType().Name}: {sw.ElapsedMilliseconds}ms, {patchSize} bytes");
}

5.3 真实业务落地示例(通过 Core DiffPipeline 使用)

大多数情况下不直接使用 Differential,而是通过 Core 的 DiffPipeline 做目录级差分:

using GeneralUpdate.Core;
using GeneralUpdate.Core.Pipeline;
using GeneralUpdate.Core.Models;
using GeneralUpdate.Differential.Differ;

// 构建端:对比新旧版本目录,生成补丁
var pipeline = new DiffPipelineBuilder()
.UseDiffer(new StreamingHdiffDiffer())
.WithParallelism(8) // CI 构建机,高并行度
.WithStopOnFirstError(true)
.WithProgress(new Progress<DiffProgress>(p =>
{
Console.WriteLine($"[Build] {p.Completed}/{p.Total} {p.CurrentFile}");
}))
.Build();

await pipeline.CleanAsync(
@"D:\builds\v1.0.0",
@"D:\builds\v1.0.1",
@"D:\patches\v1.0.0-to-v1.0.1");

// 客户端:通过 GeneralUpdateBootstrap 使用
await new GeneralUpdateBootstrap()
.SetSource(
updateUrl: "https://update.mycompany.com/api/upgrade/verification",
appSecretKey: "prod-key")
.SetOption(Option.AppType, AppType.Client)
.SetOption(Option.PatchEnabled, true)
.SetOption(Option.DiffMode, DiffMode.Parallel)
.SetOption(Option.MaxConcurrency, 4)
.UseDiffPipeline(builder => builder
.UseDiffer(new StreamingHdiffDiffer())
.WithParallelism(4))
.LaunchAsync();

6. 算法选择指南

Clean 与 Dirty 语义

术语方法输入输出常用位置
CleanCleanAsync旧文件、新文件、补丁输出路径.patch 补丁文件构建/发布阶段
DirtyDirtyAsync旧文件、输出新文件路径、补丁路径还原后的新文件客户端升级阶段

算法对比

对比维度BsdiffDifferStreamingHdiffDiffer
核心思路经典 BSDIFF 4.0,后缀排序 + 最长匹配块级 FNV-1a 哈希索引 + 字节级扩展匹配
默认压缩BZip2 (0x00)Deflate (0x01)
补丁应用自实现 BSDIFF Dirty委托给 BsdiffDiffer(BSDIF 兼容)
补丁体积更稳定,通常更小受文件变化分布影响大,块命中差时可能接近原文件大小
客户端应用速度BZip2 解压较慢Deflate 解压更快(约 1.5-5x)
生成内存全量读入新旧文件maxWindowSize 预算读入
兼容性兼容旧 BSDIFF/BZip2 补丁适合新项目

场景推荐

场景推荐
补丁体积优先BsdiffDiffer + BZip2
客户端应用速度优先StreamingHdiffDiffer(默认 Deflate)
兼容旧补丁格式BsdiffDiffer(32 字节旧头自动按 BZip2 处理)
大文件(>500MB)先压测,可能需要 StreamingHdiffDiffer + 调大 maxWindowSize
目录级批量差分通过 Core DiffPipeline,配合 WithParallelism 提升吞吐
新项目先用默认配置跑基准,再根据体积和速度需求调整

并发模型

  • 单个 IBinaryDiffer 实例线程安全(当 ICompressionProvider 线程安全时)
  • 真正的多线程差分在 Core DiffPipeline 层,通过 WithParallelism(n) 控制
  • BZip2 / Deflate 内置 provider 均为每次调用创建新流,支持并发

相关资源