C# GZip压缩与解压 C#如何对文件流进行GZip处理

来源:这里教程网 时间:2026-02-21 17:41:15 作者:

为什么直接用
GZipStream
压缩文件流会出错?

常见错误是把

GZipStream
当成“压缩器对象”反复调用,比如先
Write
Close
,结果解压时抛出
InvalidDataException
或乱码。根本原因是
GZipStream
不缓存数据,它依赖底层流的生命周期 —— 必须在写入完成后立刻刷新并关闭,否则尾部校验字节(CRC、ISIZE)没写入,解压端就无法验证完整性。

GZipStream
是装饰器(Decorator),不是独立容器;它必须包装一个可写的底层流(如
FileStream
),且该流不能被提前关闭
压缩后流长度 ≠ 原始长度,但解压时必须读到 EOF 才算完整;若压缩端没写完就断开,解压端会卡在等待更多输入 不要手动调用
Flush()
代替
Close()
——
Flush()
不保证写出 GZip 尾部,只有
Close()
Dispose()
才会

如何安全地压缩一个
FileStream
到另一个文件?

核心原则:用

using
确保嵌套流按顺序释放,外层
GZipStream
Dispose()
,再让底层
FileStream
关闭。这样尾部信息才能落盘。

using var fsIn = new FileStream("input.txt", FileMode.Open);
using var fsOut = new FileStream("input.txt.gz", FileMode.Create);
using var gzip = new GZipStream(fsOut, CompressionLevel.Optimal, leaveOpen: false);
fsIn.CopyTo(gzip); // 自动处理缓冲与结尾写入
leaveOpen: false
(默认值)表示
GZipStream.Dispose()
会同时关闭
fsOut
;设为
true
仅当你后续还要往
fsOut
写其他内容时才用
CompressionLevel.Fastest
Optimal
在小文件(Optimal
别用
Read/Write
循环手动搬运字节 ——
CopyTo
内部已优化缓冲区大小(81920 字节),更可靠

解压时为何读不到完整内容或抛出
End of Stream

典型表现:解压后文件比原文件短几字节,或者读取时突然中断。问题往往出在解压端没有正确识别 GZip 流边界 —— 特别是当输入流本身不支持

Seek
(如网络响应流、管道流)时,
GZipStream
无法预判结尾。

解压必须用
CopyTo
或循环读取直到
Read()
返回 0,不能只读一次
Read(buffer, 0, buffer.Length)
如果源流是内存流(
MemoryStream
),确保其
Position == 0
CanSeek == true
;否则
GZipStream
可能误判流结束
避免把压缩后的字节数组直接传给
GZipStream
构造函数再读 —— 必须用
new MemoryStream(bytes)
包一层,并重置
Position = 0

如何对内存中的
byte[]
做 GZip 压缩/解压而不碰磁盘?

这是高频需求,比如 HTTP 响应体压缩、序列化缓存。关键点在于:所有流必须可寻址(

CanSeek == true
),且解压前要重置位置。

// 压缩
var bytes = Encoding.UTF8.GetBytes("hello world");
using var input = new MemoryStream(bytes);
using var output = new MemoryStream();
using var gzip = new GZipStream(output, CompressionLevel.Optimal);
input.CopyTo(gzip);
var compressed = output.ToArray(); // 此时 output.Position 在末尾,ToArray() 仍能拿到全部
// 解压
using var compressedStream = new MemoryStream(compressed);
compressedStream.Position = 0; // 必须!
using var decompress = new GZipStream(compressedStream, CompressionMode.Decompress);
using var resultStream = new MemoryStream();
decompress.CopyTo(resultStream);
var decompressed = resultStream.ToArray();
MemoryStream.ToArray()
总是返回完整缓冲区,不管当前
Position
在哪;但
MemoryStream.GetBuffer()
有风险,可能包含未使用字节
解压时若忘记
compressedStream.Position = 0
GZipStream
会从末尾开始读,直接报
InvalidDataException
如果只是临时压缩小数据(Convert.ToBase64String(compressed) 编码后再传输更稳妥,避免二进制粘包问题 GZip 的边界处理非常严格,尤其在校验和与长度字段上 —— 它不像 ZIP 那样容错。任何提前关闭、位置错位、流不可寻址的操作,都会让整个链路失败。实际写的时候,宁可多包一层
using
,也不要图省事复用流对象。

相关推荐