为什么直接用 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,也不要图省事复用流对象。
