如何收缩zlib数据并找出有多少输入字节?

时间:2015-02-21 00:50:38

标签: git rust zlib

我正在Rust中构建一个git clone实现。我已经到了需要解析packfile来创建索引的部分,我差不多完成了它的解析。

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

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

Crates.io显示了2个进行zlib解压缩并且下载次数较多的板条箱:

  • libz-sys:实际上是一个问候世界,已经好几个月了
  • 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()
    );
    

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

它取决于读者的所有权,而不是参考。

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

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

PS:flate2文档提示helper trait,但我不知道这对我有什么帮助

1 个答案:

答案 0 :(得分:1)

通常,您可以将引用传递给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头在输出文件中重复两次。但是,阅读时有效。看起来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。它总是试图尽可能多地填充自己的内部缓冲区,即使某些数据不会被读取。这个可怕,讨厌的黑客导致它一次只能读取一个字节。因此,您可以在结尾处回放一个字节并重新开始。

较长期的解决方案可能是为total_in添加Stream的访问者,然后再到ZlibDecoder