GZipStream 压缩文件流出错的根本原因是未正确处理尾部校验字节(CRC、ISIZE)写入,必须通过 Close() 或 Dispose() 触发,而不能仅用 Flush();安全做法是 using 嵌套声明,确保 GZipStream 先释放再关闭底层流。
为什么直接用 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 无法预判结尾。