You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.NET中直接从ReadOnlySpan<byte>用Deflate解压缩避免内存分配?

如何在无额外内存分配的情况下用ReadOnlySpan解压缩Deflate数据

你遇到的问题在早期.NET Core版本里确实很常见——DeflateStream只能接收Stream作为输入,而直接把ReadOnlySpan<byte>转成MemoryStream通常需要调用ToArray(),这就会产生额外的内存分配,违背了你用ReadOnlySpan减少分配的初衷。下面分几种情况给你解决方案:

1. 在.NET Core 2.1中避免额外分配:自定义ReadOnlySpanStream

你可以自己实现一个包装ReadOnlySpan<byte>的Stream子类,这样就不用把Span转成byte[]再传入MemoryStream。这个自定义Stream不会分配额外内存,只是把Span的内容暴露给DeflateStream读取。

示例代码如下:

public class ReadOnlySpanStream : Stream
{
    private ReadOnlySpan<byte> _span;
    private int _position;

    public ReadOnlySpanStream(ReadOnlySpan<byte> span)
    {
        _span = span;
        _position = 0;
    }

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => _span.Length;

    public override long Position
    {
        get => _position;
        set
        {
            if (value < 0 || value > _span.Length)
                throw new ArgumentOutOfRangeException(nameof(value));
            _position = (int)value;
        }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int bytesToRead = Math.Min(count, _span.Length - _position);
        _span.Slice(_position, bytesToRead).CopyTo(buffer.AsSpan(offset));
        _position += bytesToRead;
        return bytesToRead;
    }

    // .NET Core 2.1支持Span的Read重载,实现它能进一步优化
    public override int Read(Span<byte> buffer)
    {
        int bytesToRead = Math.Min(buffer.Length, _span.Length - _position);
        _span.Slice(_position, bytesToRead).CopyTo(buffer);
        _position += bytesToRead;
        return bytesToRead;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                Position = offset;
                break;
            case SeekOrigin.Current:
                Position += offset;
                break;
            case SeekOrigin.End:
                Position = _span.Length + offset;
                break;
        }
        return Position;
    }

    public override void Flush() => throw new NotSupportedException();
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
}

然后你就可以这样使用它,完全避免输入侧的额外内存分配:

if (IsCompressed)
{
    var span = ReadBlock();
    using var stream = new ReadOnlySpanStream(span);
    using var deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
    
    // 读取解压缩后的数据到Data
    using var ms = new MemoryStream();
    deflateStream.CopyTo(ms);
    Data = ms.ToArray();
}

注:这里最终还是要生成byte[](因为你的Data是byte[]类型),但至少省掉了原方案中buffer.ToArray()这一步的额外分配。

2. 升级到更高版本的.NET(推荐)

如果你能升级到.NET 5或更高版本,会有更简洁高效的方案:

  • .NET 5+的MemoryStream支持直接接收ReadOnlyMemory<byte>作为输入(ReadOnlySpan<byte>可以无分配转换为ReadOnlyMemory<byte>),无需额外创建byte[]:
    if (IsCompressed)
    {
        var memory = ReadBlock().ToArray().AsMemory(); // 不对,直接用span转memory:ReadBlock().AsMemory()
        using var stream = new MemoryStream(memory);
        using var deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
        
        using var ms = new MemoryStream();
        deflateStream.CopyTo(ms);
        Data = ms.ToArray();
    }
    
  • 到.NET 6+,DeflateStream还新增了支持Span<byte>的Read重载,进一步优化了内存操作的效率;.NET 7+更是提供了更直接的Span级压缩/解压缩API,完全不需要Stream包装。

3. 使用第三方压缩库

如果升级.NET版本不可行,你可以考虑使用第三方库,很多库都支持直接从ReadOnlySpan<byte>解压缩数据:

  • SharpZipLib:老牌.NET压缩库,支持直接用Span处理Deflate数据,无需Stream。

示例代码(需安装SharpZipLib NuGet包):

using ICSharpCode.SharpZipLib.Zip.Compression;

if (IsCompressed)
{
    var span = ReadBlock();
    var inflater = new Inflater();
    inflater.SetInput(span);

    // 预估解压缩后的大小(若不确定可动态扩容)
    var outputBuffer = new byte[1024 * 16];
    int bytesInflated = inflater.Inflate(outputBuffer);
    
    // 根据实际解压长度调整数组
    Data = outputBuffer.Take(bytesInflated).ToArray();
}

这个方式完全绕开了Stream,直接用Span处理,没有中间内存分配。


总结一下:

  • 必须留在.NET Core 2.1:用自定义ReadOnlySpanStream避免输入侧的额外分配。
  • 可升级.NET版本:优先升到.NET 5+,利用官方完善的Span/Stream集成API。
  • 允许用第三方库:SharpZipLib这类工具可以直接操作Span,效率更高。

内容的提问来源于stack exchange,提问作者Lukas Kabrt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:35:22