C#中如何获取解压后GZipStream的Position与Length?
哈哈,这个坑我之前踩过!你遇到的NotSupportedException完全是因为GZipStream的设计特性——它根本不支持Position和Length这俩属性,咱们来一步步解决这个问题。
先搞懂为什么会报错
先明确官方文档里的核心说明:
GZipStream.Length属性完全不被支持,调用时一定会抛出NotSupportedException(这是重写自Stream基类的行为)GZipStream.Position属性同样不支持读取或设置,因为它是流式处理的解压流——数据是边读边解压的,没办法提前知道解压后的总长度,也不支持随意跳转位置(毕竟压缩数据是连续流式的,没法直接定位到某个解压后的位置)
所以你想用Position和Length来判断是否到文件末尾,从根上就行不通。
正确判断读取结束的方法
咱们换个思路,通过读取操作的返回值来判断流是否结束,这也是流式处理的标准做法:
方法1:用BinaryReader的基础读取方法判断
如果是逐个字节读取,用ReadByte()——当它返回-1时,就说明已经读到流的末尾了:
using (var fileStream = new FileStream("target.gz", FileMode.Open)) using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress)) using (var binaryReader = new BinaryReader(gzipStream)) { int currentByte; // 循环读取直到返回-1 while ((currentByte = binaryReader.ReadByte()) != -1) { // 处理读取到的字节 Console.WriteLine($"读取到字节:{currentByte}"); } }
如果是按块读取(效率更高),用Read(byte[] buffer, int offset, int count)——当它返回0时,说明没有更多数据可读了:
byte[] buffer = new byte[4096]; // 4KB的缓冲区,可根据需求调整 int bytesRead; using (var fileStream = new FileStream("target.gz", FileMode.Open)) using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress)) using (var binaryReader = new BinaryReader(gzipStream)) { while ((bytesRead = binaryReader.Read(buffer, 0, buffer.Length)) > 0) { // 处理本次读取到的bytesRead个字节 // 比如写入另一个文件,或者解析数据 Console.WriteLine($"本次读取了{bytesRead}字节"); } }
方法2:如果必须要知道解压后的总长度(可选)
如果你业务上一定要提前知道解压后的数据大小,有个小技巧:标准GZip文件的末尾会用4字节存储原始未压缩数据的长度(注意:这个长度是32位无符号整数,所以只适用于原始文件≤4GB的情况)。你可以先读取这个值,再解压:
long uncompressedSize = 0; using (var fileStream = new FileStream("target.gz", FileMode.Open)) { // 跳到文件末尾的前4字节位置 fileStream.Seek(-4, SeekOrigin.End); byte[] sizeBuffer = new byte[4]; fileStream.Read(sizeBuffer, 0, 4); // 把字节数组转成无符号整数 uncompressedSize = BitConverter.ToUInt32(sizeBuffer, 0); // 回到文件开头准备解压 fileStream.Seek(0, SeekOrigin.Begin); using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress)) using (var binaryReader = new BinaryReader(gzipStream)) { // 直接读取全部解压后的数据 byte[] fullData = new byte[uncompressedSize]; binaryReader.Read(fullData, 0, fullData.Length); // 处理完整数据 Console.WriteLine($"解压后总大小:{uncompressedSize}字节"); } }
⚠️ 注意:如果原始文件超过4GB,这个方法就会失效,因为32位整数存不下这么大的数值。这种情况下还是老老实实用流式读取的方法更稳妥。
最后总结一下
- 彻底放弃用
GZipStream的Position和Length属性判断末尾,这俩属性本来就不是给它设计的 - 用
BinaryReader.ReadByte() == -1或者BinaryReader.Read(...) == 0来判断流是否结束,这是流式处理的标准姿势 - 非要知道解压后长度的话,用GZip文件末尾的4字节字段,但要注意4GB的限制
内容的提问来源于stack exchange,提问作者Russell Butler
相关产品推荐
相关产品推荐

