git - 我如何压缩 zlib 数据并找出有多少输入字节?

标签 git rust zlib

我正在用 Rust 构建一个 git clone 实现。我已经到了需要解析包文件以创建索引的部分,我几乎完成了解析。

packfile 中的每个对象都包含一个 header (我已经正确解析了),然后是 zlib 压缩的内容。

值得注意的是, header 中存储的大小是解压缩 的大小,因此比我们必须跳过才能到达下一个 header 的实际数据大。

Crates.io 显示了 2 个进行 zlib 解压缩并且有多个下载的 crate:

  • libz-sys:实际上是一个 hello world,几个月来一直如此
  • flate2:这可以轻松正确地缩小数据:

    print!("Object type {} size {}", obj_type as u8, obj_size);
    
    println!(" data:\n{}",
        String::from_utf8(
            ZlibDecoder::new(data).read_exact(obj_size as usize).unwrap()
        ).unwrap()
    );
    

问题来了。在此之后我需要开始读取下一个对象的 header ,但是 ZlibDecoder 没有提供任何方法来检测输入有多大。

它在输入时拥有读者的所有权,而不是引用。

因此,即使我有对象的输出大小(实际上所有对象的数据),因为我不知道输入大小,我无法开始读取下一个对象标题。

如何获得达到预期输出大小所需的压缩输入字节数?如果可能,我想避免使用 FFI 调用原生 zlib。

PS:flate2 文档建议 helper trait但我不知道这将如何或是否对我有帮助

最佳答案

通常,您可以将引用传递给 Reader/Writer(通过 ByRefReaderByRefWriter )以允许将适配器添加到流而不会失去对其的控制。像这样的东西应该工作:

#![feature(io,path,env)]

extern crate flate2;

use flate2::CompressionLevel;
use flate2::writer::ZlibEncoder;
use flate2::reader::ZlibDecoder;

use std::env;
use std::old_io::File;
use std::old_io::{ByRefReader,ByRefWriter};
use std::old_path::Path;

fn main() {
    let path = "./data";
    let write = env::var("WRITE").is_ok();

    if write {
        println!("Writing to {}", path);
        let mut f = File::create(&Path::new(path)).unwrap();

        fn write_it<W>(w: &mut W, s: &str) where W: Writer {
            let mut z = ZlibEncoder::new(ByRefWriter::by_ref(w), CompressionLevel::Default);
            z.write_all(s.as_bytes()).unwrap();
        }

        write_it(&mut f, "hello world");
        write_it(&mut f, "goodbye world");
    } else {
        println!("Reading from {}", path);
        let mut f = File::open(&Path::new(path)).unwrap();

        fn read_it<R>(r: &mut R) -> String where R: Reader {
            let mut z = ZlibDecoder::new(ByRefReader::by_ref(r));
            z.read_to_string().unwrap()

        }

        println!("{}", read_it(&mut f));
        println!("{}", read_it(&mut f));
    }
}

确实 用于编写 - 我看到 Zlib header 在输出文件中重复了两次。但是,它在阅读时不起作用。看起来 reader::ZlibDecoder 可能会一直消耗到底层 Reader 的末尾。这可能是 flate2 库中的错误或疏忽。盯着the source看了几分钟虽然没有显示出任何明显的东西。

编辑

这里有一个“有效”的糟糕技巧:

fn read_it<R>(r: &mut R) -> String where R: Reader {
    let mut z = ZlibDecoder::new_with_buf(ByRefReader::by_ref(r), Vec::with_capacity(1));
    z.read_to_string().unwrap()
}

println!("{}", read_it(&mut f));
f.seek(-1, std::old_io::SeekStyle::SeekCur);
println!("{}", read_it(&mut f));

问题出现是因为flate2是a bit greedy in how it reads from the reader .它总是尝试尽可能多地填充自己的内部缓冲区,即使其中一些数据不会被读取。这个可怕的、讨厌的 hack 导致它一次只能读取一个字节。因此,您可以在末尾倒回一个字节并重新开始。

一个长期的解决方案可能是为 total_in 添加一个访问器最多 Stream然后直到到达 ZlibDecoder

关于git - 我如何压缩 zlib 数据并找出有多少输入字节?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28640738/

相关文章:

c - 如何修复对膨胀/收缩函数的 undefined reference ?

linux - Linux 中的 git 和硬链接(hard link)

git - 将错误修复提交到 master 和 branch 的最佳实践

git - catch 22 具有私有(private)仓库的 git 权限

git - 历史 Subversion 修订更改后修复 git-svn 存储库

rust - 如何创建一个构建器,该构建器采用为 `&str` 的切片实现 AsRef 的类型?

rust - 火箭内的 Handlebars helper

struct - 我怎样才能使某些结构字段可变?

ruby - 如何逐行读取 Ruby 1.8.7 中的 gzip 文件?

linux - 使用 Cygwin 安装 Kaldi,zlib 出错