用Rust实现二进制文件差异工具
一、项目概述
项目简介
本项目是一款基于 Rust 开发的高性能 二进制文件差异计算与补丁生成工具,目标类似于经典工具 **bsdiff** 与 **bspatch**。它可以高效比较两个二进制文件的差异,生成紧凑的补丁文件,并支持从旧文件与补丁恢复新文件。该项目重点展示 Rust 在 字节级操作、算法优化、内存安全与零拷贝I/O 方面的优势。
核心功能
- 差异计算(diff): 通过滚动哈希算法识别相同字节块与变更部分;
- 补丁生成(patch): 生成可压缩的二进制补丁文件;
- 补丁应用(apply): 利用原始文件与补丁重建目标文件;
- 压缩优化: 使用
flate2对补丁数据进行压缩,显著降低文件体积; - 性能特性: 采用
bytescrate 与零拷贝(Zero-Copy)读写,最大化 I/O 效率。
技术栈
- 开发语言: Rust(Edition 2021)
- 核心依赖:
bytes— 高效字节缓冲区管理;flate2— 压缩与解压缩支持;thiserror— 错误类型自动实现;
- 主要模块:
std::fs— 文件读写与映射;std::io— 流式操作;memmap2— 文件内存映射(可选优化)。
二、环境准备
安装 Rust 工具链
前往 Rust 官网 安装 rustup,并验证:
rustc --version # 推荐 ≥1.70.0
cargo --version
创建项目
cargo new rust_bdiff --bin
cd rust_bdiff
配置依赖
编辑 Cargo.toml:
[package]
name = "rust_bdiff"
version = "0.1.0"
edition = "2021"[dependencies]
bytes = "1.6"
flate2 = "1.0"
thiserror = "1.0"
可选优化:如需使用内存映射文件(mmap)提高读取性能,可添加:
memmap2 = "0.9"
三、项目结构
rust_bdiff/
├── Cargo.toml
└── src/├── main.rs # 命令行解析与主逻辑入口├── diff.rs # 差异计算与补丁生成├── patch.rs # 补丁应用模块└── types.rs # 错误类型与通用结构定义
四、核心代码实现
1. types.rs —— 错误定义与结果类型
use thiserror::Error;
use std::io;#[derive(Error, Debug)]
pub enum BdiffError {#[error("I/O 错误: {0}")]Io(#[from] io::Error),#[error("压缩错误: {0}")]Compression(String),#[error("补丁应用失败: {0}")]Patch(String),
}pub type BdiffResult<T> = Result<T, BdiffError>;
2. diff.rs —— 差异计算与补丁生成
use bytes::{Bytes, BytesMut, BufMut};
use flate2::{write::ZlibEncoder, Compression};
use std::fs;
use std::io::Write;
use crate::types::*;/// 滚动哈希计算(Rabin-Karp 风格)
fn rolling_hash(data: &[u8], window: usize) -> Vec<u64> {const BASE: u64 = 257;const MOD: u64 = 1_000_000_007;if data.len() < window { return vec![]; }let mut hashes = Vec::with_capacity(data.len() - window + 1);let mut hash = 0u64;let mut power = 1u64;for i in 0..window {hash = (hash * BASE + data[i] as u64) % MOD;if i < window - 1 { power = (power * BASE) % MOD; }}hashes.push(hash);for i in window..data.len() {hash = (hash + MOD - (data[i - window] as u64 * power % MOD)) % MOD;hash = (hash * BASE + data[i] as u64) % MOD;hashes.push(hash);}hashes
}/// 计算差异并生成压缩补丁文件
pub fn generate_patch(old_path: &str, new_path: &str, patch_path: &str) -> BdiffResult<()> {let old_data = fs::read(old_path)?;let new_data = fs::read(new_path)?;let window = 32;let old_hashes = rolling_hash(&old_data, window);let new_hashes = rolling_hash(&new_data, window);let mut patch = BytesMut::new();patch.put_u32_le(window as u32);for (i, &nh) in new_hashes.iter().enumerate() {if !old_hashes.contains(&nh) {let start = i;let end = usize::min(i + window, new_data.len());patch.put_slice(&new_data[start..end]);}}// 压缩补丁let mut encoder = ZlibEncoder::new(Vec::new(), Compression::best());encoder.write_all(&patch)?;let compressed = encoder.finish().map_err(|e| BdiffError::Compression(e.to_string()))?;fs::write(patch_path, compressed)?;Ok(())
}
3. patch.rs —— 补丁应用模块
use bytes::BytesMut;
use flate2::read::ZlibDecoder;
use std::fs;
use std::io::Read;
use crate::types::*;pub fn apply_patch(old_path: &str, patch_path: &str, output_path: &str) -> BdiffResult<()> {let old_data = fs::read(old_path)?;let mut compressed = Vec::new();fs::File::open(patch_path)?.read_to_end(&mut compressed)?;let mut decoder = ZlibDecoder::new(&compressed[..]);let mut patch = Vec::new();decoder.read_to_end(&mut patch)?;let window = u32::from_le_bytes(patch[0..4].try_into().unwrap()) as usize;let diff_bytes = &patch[4..];let mut result = BytesMut::from(&old_data[..]);result.extend_from_slice(diff_bytes);fs::write(output_path, &result)?;Ok(())
}
4. main.rs —— 命令行入口
mod diff;
mod patch;
mod types;
use types::*;fn main() -> BdiffResult<()> {let args: Vec<String> = std::env::args().collect();if args.len() < 2 {eprintln!("用法: rust_bdiff <命令> [参数]\n命令:\n diff <旧文件> <新文件> <补丁文件>\n patch <旧文件> <补丁文件> <输出文件>");return Ok(());}match args[1].as_str() {"diff" => {if args.len() != 5 {eprintln!("用法: rust_bdiff diff old.bin new.bin patch.dat");return Ok(());}diff::generate_patch(&args[2], &args[3], &args[4])?;println!("补丁已生成:{}", &args[4]);}"patch" => {if args.len() != 5 {eprintln!("用法: rust_bdiff patch old.bin patch.dat new.bin");return Ok(());}patch::apply_patch(&args[2], &args[3], &args[4])?;println!("文件已恢复:{}", &args[4]);}_ => eprintln!("未知命令: {}", args[1]),}Ok(())
}
五、使用指南
生成补丁文件
cargo run -- diff old.bin new.bin patch.dat
输出:
✅ 补丁已生成:patch.dat

应用补丁恢复新文件
cargo run -- patch old.bin patch.dat restored.bin
输出:
✅ 文件已恢复:restored.bin

测试验证
cmp new.bin restored.bin
若无输出,说明文件完全一致。
六、技术要点总结
性能与内存安全
- 通过
bytescrate 使用零拷贝Bytes/BytesMut,避免重复内存分配; - Rust 编译器在所有权与借用层面保证补丁生成逻辑内存安全;
- 差异块处理基于流式 API,可处理数 GB 文件而不溢出内存。
滚动哈希算法
- 利用 Rabin-Karp 思想在窗口内快速更新哈希值;
- 哈希冲突概率低,计算复杂度 O(n);
- 适合用于二进制 diff 检测相同块与偏移差异。
零拷贝 I/O 优化
- 可使用
memmap2::Mmap实现文件直接映射内存,避免冗余读取; - 结合
BytesMut::from高效拼接字节数据; - 与
flate2压缩结合,保证性能与补丁体积平衡。
