【问题标题】:C# MemoryStream & GZipInputStream: Can't .Read more than 256 bytesC# MemoryStream & GZipInputStream: Can't .Read more than 256 bytes
【发布时间】:2020-02-07 00:32:24
【问题描述】:

我在使用 SharpZipLib 的 GZipInputStream 编写未压缩的 GZIP 流时遇到问题。我似乎只能获得 256 字节的数据,而其余的数据没有被写入并保持为零。已检查压缩流 (compressedSection) 并且所有数据都在那里(1500+ 字节)。解压过程的sn-p如下:

int msiBuffer = 4096;
using (Stream msi = new MemoryStream(msiBuffer))
{
    msi.Write(compressedSection, 0, compressedSection.Length);
    msi.Position = 0;
    int uncompressedIntSize = AllMethods.GetLittleEndianInt(uncompressedSize, 0); // Gets little endian value of uncompressed size into an integer

    // SharpZipLib GZip method called
    using (GZipInputStream decompressStream = new GZipInputStream(msi, uncompressedIntSize))
    {
        using (MemoryStream outputStream = new MemoryStream(uncompressedIntSize))
        {
            byte[] buffer = new byte[uncompressedIntSize];
            decompressStream.Read(buffer, 0, uncompressedIntSize); // Stream is decompressed and read         
            outputStream.Write(buffer, 0, uncompressedIntSize);
            using (var fs = new FileStream(kernelSectionUncompressed, FileMode.Create, FileAccess.Write))
            {
                fs.Write(buffer, 0, buffer.Length);
                fs.Close();
            }
            outputStream.Close();
        }
        decompressStream.Close();

所以在这个sn-p中:

1) 压缩部分传入,准备解压。

2) 未压缩输出的预期大小(以 2 字节 little-endian 值存储在文件的标头中)通过一种方法将其转换为整数。由于它不是压缩的 GZIP 文件的一部分,因此该标头已被较早地删除。

3) SharpLibZip 的 GZIP 流是用压缩文件流 (msi) 和一个等于 int uncompressedIntSize 的缓冲区声明的(也用静态值 4096 进行了测试)。

4) 我设置了一个 MemoryStream 来处理将输出写入文件,因为 GZipInputStream 没有读/写功能;它将预期的解压缩文件大小作为参数(容量)。

5) 流的读/写需要 byte[] 数组作为第一个参数,所以我设置了一个 byte[] 数组,它有足够的空间来获取解压缩输出的所有字节(本例中为 3584 字节,派生自 uncompressedIntSize)。

6) int GzipInputStream decompressStream 使用 .Read 将缓冲区作为第一个参数,从偏移量 0 开始,使用 uncompressedIntSize 作为计数。检查这里的参数,缓冲区数组仍然有 3584 字节的容量,但只获得了 256 字节的数据。其余为零。

看起来 .Read 的输出被限制为 256 字节,但我不确定在哪里。 Streams 中是否有我遗漏的东西,或者这是 .Read 的限制?

【问题讨论】:

  • 任何时候你打电话给stream.Read没有读取结果:这几乎肯定是错误; Read 保证读取 uncompressedIntSize 字节;如果流为空,则只需要读取 0 个字节,否则需要读取 任何数字 uncompressedIntSize;每次可以读取 1 个字节;你需要循环,还是打电话给decompressStream.CopyTo(outputStream);

标签: c# memorystream gzipinputstream


【解决方案1】:

这个问题原来是我之前在发布的代码中的疏忽:

我正在使用的文件有 27 个 GZipped 部分,但它们每个都有一个标头,如果 GZipInput 流命中其中任何一个,它们都会破坏 Gzip 解压缩。打开base文件时,每次都是从头开始(调整6避免第一个header),而不是到下一个post-head偏移:

brg.BaseStream.Seek(6, SeekOrigin.Begin);

代替:

brg.BaseStream.Seek(absoluteSectionOffset, SeekOrigin.Begin);

这意味着提取的压缩数据是第一个无标题部分 + 第二部分的一部分及其标题的混合物。由于第一部分的长度为 256 字节,没有标头,因此 GZipInput 流正确地解压缩了这部分。但在那之后是 6 字节的标头将其破坏,导致其余输出为 00。

发生这种情况时,GZipInput 流没有抛出明确的错误,所以我错误地认为原因是 .Read 或流中的某些东西保留了上一次传递的数据。麻烦您了。

【讨论】:

    【解决方案2】:

    从流中读取时需要循环懒惰的方式大概是:

    decompressStream.CopyTo(outputStream);
    

    (但这并不能保证在 uncompressedIntSize 字节之后停止 - 它会尝试读取到 decompressStream 的末尾)

    更手动的版本(尊重强加的长度限制)是:

    const int BUFFER_SIZE = 1024; // whatever
    var buffer = ArrayPool<byte>.Shared.Rent(BUFFER_SIZE);
    try
    {
        int remaining = uncompressedIntSize, bytesRead;
        while (remaining > 0 && // more to do, and making progress
            (bytesRead = decompressStream.Read(
            buffer, 0, Math.Min(remaining, buffer.Length))) > 0)
        {
            outputStream.Write(buffer, 0, bytesRead);
            remaining -= bytesRead;
        }
        if (remaining != 0) throw new EndOfStreamException();
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer);
    }
    

    【讨论】:

    • 值得注意的是,ArrayPool 仅在 .NET Core 和 .NETStandard 2.1+ 中可用
    • @pete 不,那是错误的。它转到 net45 和 netstandard1.1(加上一些 UAP 等),这意味着“几乎任何东西”nuget.org/packages/System.Buffers
    • 我刚刚离开这里的文档:docs.microsoft.com/en-us/dotnet/api/…
    • @PeteGarafano kind 对我来说解释该页面的方式是:文档不完整;我怀疑解释页面的更公平的方式是说:文档是错误的
    猜你喜欢
    • 1970-01-01
    • 2022-11-22
    • 2016-03-22
    • 2018-08-10
    • 2014-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多