Rust中如何将单个Reader的数据分流到多个Writer?
问题描述
我有如下Rust函数(为简洁省略了错误处理):
pub fn build_tar(dir: Path) -> Result<(), String> { let file = File::create("archive.tar.br"); let brotli = brotli::CompressorWriter::new(file); let archive = tar::Builder::new(brotli); for file in fs::read_dir(dir) { archive.append(file); } archive.finish(); }
现在我想在构建归档文件且压缩前计算sha256摘要,使用sha2::Sha256(它提供了可用于io::copy的Writer能力),需要将归档的数据同时写入哈希Writer和压缩Writer。我目前的实现方式如下:
pub fn build_tar(dir: Path) -> Result<(), String> { let file = File::create("archive.tar.br"); let brotli = brotli::CompressorWriter::new(file); let hash = sha2::Sha256::new(); let archive1 = tar::Builder::new(brotli); let archive2 = tar::Builder::new(hash); for file in fs::read_dir(dir) { archive1.append(file); archive2.append(file); } archive1.finish(); archive2.finish(); HEXLOWER.encode(hash.finalize().as_ref()) }
这种写法符合Rust惯用风格吗?是否合理?我感觉不太对……
另外我曾搜索“分流Reader”或“复制Reader”相关内容但无结果,同时查阅sha2文档也没找到Sha256::new()的实例实现Write trait的相关说明。
解答
你的当前写法既不合理也不符合Rust惯用风格,核心问题有三点:
- 重复构建归档的性能浪费:创建两个
tar::Builder分别处理同一批文件,相当于把整个归档流程执行了两次,会双倍消耗CPU和IO资源,完全没必要。 - 所有权冲突问题:
hash被archive2接管后,archive2.finish()会消耗掉内部的Sha256实例,后续尝试调用hash.finalize()会直接编译报错——你无法再访问已被转移所有权的变量。 Sha256的Write实现误解:sha2::Sha256本身没有直接实现std::io::Write,它是通过digest::Digesttrait的update方法接收数据,或启用digest的write特性后通过适配层实现std::io::Write。
正确的思路是使用分流Writer(Tee Writer):让tar::Builder只执行一次归档写入,同时把数据转发给哈希计算和压缩两个目标。下面提供两种实现方案:
方案1:自定义分流Writer
自己实现一个简单的TeeWriter,同时向两个Write目标写入数据:
use std::io::{self, Write}; use sha2::{Sha256, Digest}; use tar::Builder; use brotli::CompressorWriter; use std::fs::{self, File}; use std::path::Path; use hex; // 自定义分流Writer,同时写入两个Write实例 struct TeeWriter<W1, W2> { w1: W1, w2: W2, } impl<W1: Write, W2: Write> Write for TeeWriter<W1, W2> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { // 先写入第一个目标 let n1 = self.w1.write(buf)?; // 确保第二个目标写入相同长度的数据 let n2 = self.w2.write(&buf[..n1])?; if n1 != n2 { return Err(io::Error::new(io::ErrorKind::Other, "分流Writer写入长度不匹配")); } Ok(n1) } fn flush(&mut self) -> io::Result<()> { self.w1.flush()?; self.w2.flush()?; Ok(()) } } pub fn build_tar(dir: &Path) -> Result<String, String> { // 创建压缩文件 let file = File::create("archive.tar.br").map_err(|e| e.to_string())?; let brotli = CompressorWriter::new(file, 4096, 11, 0); // 初始化哈希计算器 let mut hash = Sha256::new(); // 创建分流Writer,同时指向压缩器和哈希计算器 let mut tee = TeeWriter { w1: brotli, w2: &mut hash, }; // 初始化归档构建器,写入分流Writer let mut archive = Builder::new(&mut tee); // 遍历目录并添加文件到归档 for entry in fs::read_dir(dir).map_err(|e| e.to_string())? { let entry = entry.map_err(|e| e.to_string())?; let path = entry.path(); archive.append_path(&path).map_err(|e| e.to_string())?; } // 完成归档写入 archive.finish().map_err(|e| e.to_string())?; // 计算并返回十六进制哈希值 Ok(hex::encode(hash.finalize())) }
方案2:使用现成的tee crate
如果不想自己实现,可以直接使用社区维护的tee crate:
- 在
Cargo.toml中添加依赖:
[dependencies] tee = "0.1" sha2 = "0.10" tar = "0.4" brotli = "3.4" hex = "0.4"
- 修改代码:
use std::io::Write; use sha2::{Sha256, Digest}; use tar::Builder; use brotli::CompressorWriter; use std::fs::{self, File}; use std::path::Path; use hex; use tee::TeeWriter; pub fn build_tar(dir: &Path) -> Result<String, String> { let file = File::create("archive.tar.br").map_err(|e| e.to_string())?; let brotli = CompressorWriter::new(file, 4096, 11, 0); let mut hash = Sha256::new(); // 使用crate提供的TeeWriter分流数据 let mut tee = TeeWriter::new(brotli, &mut hash); let mut archive = Builder::new(&mut tee); // 遍历添加文件逻辑同方案1 for entry in fs::read_dir(dir).map_err(|e| e.to_string())? { let entry = entry.map_err(|e| e.to_string())?; let path = entry.path(); archive.append_path(&path).map_err(|e| e.to_string())?; } archive.finish().map_err(|e| e.to_string())?; Ok(hex::encode(hash.finalize())) }
关于Sha256的Write实现说明
sha2::Sha256本身没有实现std::io::Write,但可以通过两种方式适配:
- 直接调用
Digesttrait的update方法接收字节数据(如上述代码所示)。 - 启用
digestcrate的write特性,使用digest::io::Writer将Sha256包装为std::io::Write实例:
在Cargo.toml中修改依赖:
代码中使用:digest = { version = "0.10", features = ["write"] }use digest::io::Writer; let mut hash_writer = Writer::new(Sha256::new()); // 此时hash_writer可作为std::io::Write使用
内容的提问来源于stack exchange,提问作者ammut
相关产品推荐
相关产品推荐

