1

我正在尝试按照此处列出的包文件格式从 git pack 文件中读取 git 对象。一旦我遇到压缩数据,我就会遇到问题。我正在尝试使用System.IO.Compression.DeflateStream来解压缩 zlib 压缩对象。我基本上通过跳过前 2 个字节来忽略 zlib 标头。无论如何,第一个对象的这 2 个字节是 789C。现在麻烦开始了。

1)我只知道解压对象的大小。DeflateStream 上的 Read 方法文档指出它“将许多解压缩的字节读取到指定的字节数组中”。这就是我想要的,但是我确实看到人们将此计数设置为压缩数据的大小,我们中的一个人做错了。

2)我认为我得到的数据是正确的(看起来正确的人类可读数据),但是它正在推进底层流,我一直把它给到最后!例如,我要求它提供 187 个解压缩字节,然后一直读取剩余的 212 个字节直到流的末尾。因为整个流是 228 字节,并且在 deflate 读取 187 字节结束时流的位置现在是 228。我不能向后寻找,因为我不知道压缩数据的结尾在哪里,并且也不是我使用的所有流都是可搜索的。这是消耗整个流的预期行为吗?

4

2 回答 2

1

我正在做与 OP 完全相同的事情(读取 git pack 文件),并设法解决了这个问题。

根据 Mark Adler在这里的评论,DeflateStream确实是脑死亡和无用的,因为是的,它确实读取了压缩数据之外的字节。查看这里的源代码,它以 8K 块读取输入数据:-/

但是,DeflateStream实例有一个私有成员_inflater,它有一个私有成员_zlibStream,它有一个属性AvailIn,它返回输入缓冲区中可用的字节数。IOW,这是已读取的字节数太多,因此通过使用反射来获取这些私有部分,我们可以将文件指针向后移动那么多字节,以将其返回到应该离开的位置,即刚刚超过压缩数据的末尾。

此代码是 F#,但应该清楚发生了什么:

// zstream is the DeflateStream instance
let inflater = typeof<DeflateStream>.GetField( "_inflater", BindingFlags.NonPublic ||| BindingFlags.Instance ).GetValue( zstream )
let zlibStream = inflater.GetType().GetField( "_zlibStream", BindingFlags.NonPublic ||| BindingFlags.Instance ).GetValue( inflater )
let availInMethod = zlibStream.GetType().GetProperty( "AvailIn" ).GetMethod
let availIn: uint32 = unbox( availInMethod.Invoke( zlibStream, null ) )
// inp is the input file
inp.Seek( -(int64 availIn) + 4L, SeekOrigin.Current ) |> ignore

我认为 4 字节的调整是因为 zlib 里面有一个校验和......

于 2022-03-02T10:10:41.307 回答
-1

根据您引用的页面(我自己不熟悉这种文件格式),每个数据块都由文件索引中的偏移字段索引。由于您知道每个数据块之前的类型和数据长度字段的长度,并且您知道下一个块的偏移量,因此您也知道每个数据块的长度(即压缩字节的长度)。

也就是说,每个数据块的长度只是下一个块的偏移量减去当前块的偏移量,然后减去类型和数据长度字段的长度(无论是多少字节......根据文档,它是可变的,但您当然可以在阅读时计算该长度)。

所以:

1)我只知道解压对象的大小。DeflateStream 上的 Read 方法文档指出它“将许多解压缩的字节读取到指定的字节数组中”。这就是我想要的,但是我确实看到人们将此计数设置为压缩数据的大小,我们中的一个人做错了。

文档是正确的。DeflateStream是 的子类Stream,并且必须遵循该类的规则。由于输出请求的字节数的Read()方法Stream,这些必须是未压缩的字节。

请注意,根据上述内容,您确实知道压缩对象的大小。它不存储在文件中,但您可以从文件存储的内容中获取该信息。

2)我认为我得到的数据是正确的(看起来正确的人类可读数据),但是它正在推进底层流,我一直把它给到最后!例如,我要求它提供 187 个解压缩字节,然后一直读取剩余的 212 个字节直到流的末尾。因为整个流是 228 字节,并且在 deflate 读取 187 字节结束时流的位置现在是 228。我不能向后寻找,因为我不知道压缩数据的结尾在哪里,并且也不是我使用的所有流都是可搜索的。这是消耗整个流的预期行为吗?

是的,我希望这会发生。或者至少,我希望会发生一些缓冲,所以即使它没有一直读取到流的末尾,我希望它至少读取压缩数据末尾的一些字节.

在我看来,您至少有两种选择:

  1. 对于每个数据块,计算数据的长度(如上所述),将其读入独立MemoryStream对象,然后从该流中解压缩数据而不是原始数据。
  2. 或者,直接从源流中解压缩,使用索引中提供的偏移量在读取时查找每个数据块。当然,这不适用于不可搜索的流,您指出它在您的场景中发生。因此,此选项不适用于您方案中的所有情况。
于 2015-08-08T00:36:59.817 回答