我有一个文件,可以像这样创建:
stream.Write(headerBytes, 0, headerBytes.Count);
using (var gz = new GZipStream(stream, Compress, leaveOpen: true);
{
gz.Write(otherBytes, 0, otherBytes.Count);
}
stream.Write(moreBytes, 0, moreBytes.Count);
现在,当读取文件时,例如
stream.Read(headerBytes, 0, headerBytes.Count);
// in reality I make sure that indeed headerBytes.Count get read,
// something the above line omits
using (var gz = new GZipStream(stream, Decompress, leaveOpen: true)
{
do { /* use buffer... */}
while ((bytesRead = gz.Read(buffer, 0, buffer.Length)) != 0);
}
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
// use buffer...
事实证明,GZipStream
(DeflateStream
也是如此)从stream
读取 16384 字节,而不是我检查的实际 13293 个压缩字节。
假设我事先既不知道文件压缩部分的大小,也不知道压缩数据后面的字节数,有没有办法使用 GzipStream/DeflateStream
- 所以它只从
stream
读取压缩的数据 - 或者至少弄清楚压缩数据部分的大小是多少,以便我可以手动
stream.Position -= actuallyRead - compressedSize
?
该接口似乎没有提供执行所需操作的方法,这是不使用 的众多原因之一。NET的GZipStream或DeflateStream。
您应该改用 DotNetZip。
这个答案相当于一个丑陋的解决方法。我不是特别喜欢它,但它确实有效(除非它不起作用),即使只是为了GZipStream
.
- 或者至少弄清楚压缩数据部分的大小是多少,这样我就可以
stream.Position -= actuallyRead - compressedSize
手动地?
由于每个 gzip 文件(实际上每个 gzip 成员)都以
+---+---+---+---+---+---+---+---+
| CRC32 | ISIZE |
+---+---+---+---+---+---+---+---+
CRC32
This contains a Cyclic Redundancy Check value of the
uncompressed data
ISIZE
This contains the size of the original (uncompressed) input
data modulo 2^32.
我可以只使用未压缩的大小(模块 2^32),我在关闭GzipStream
后知道,并在流中向后寻找,直到找到与之匹配的那 4 个字节。
为了使它更健壮,我还应该在解压缩时计算 CRC32,并在流中向后寻找形成正确 CRC32 和 ISIZE 的 8 个字节之后。
丑陋,但我确实警告过你。
<讽刺>我多么喜欢封装。将所有有用的东西封装起来,给我们留下一个解压缩的 Stream,它完全适用于全知的 API 设计人员预见的一个用例.讽刺>
下面是到目前为止有效的快速SeekBack
实现:
/// <returns>the number of bytes sought back (including bytes.Length)
/// or 0 in case of failure</returns>
static int SeekBack(Stream s, byte[] bytes, int maxSeekBack)
{
if (maxSeekBack != -1 && maxSeekBack < bytes.Length)
throw new ArgumentException("maxSeekBack must be >= bytes.Length");
int soughtBack = 0;
for (int i = bytes.Length - 1; i >= 0; i--)
{
while ((maxSeekBack == -1 || soughtBack < maxSeekBack)
&& s.Position > i)
{
s.Position -= 1;
// as we are seeking back, the following will never become
// -1 (EOS), so coercing to byte is OK
byte b = (byte)s.ReadByte();
s.Position -= 1;
soughtBack++;
if (b == bytes[i])
{
if (i == 0)
return soughtBack;
break;
}
else
{
var bytesIn = (bytes.Length - 1) - i;
if (bytesIn > 0) // back to square one
{
soughtBack -= bytesIn;
s.Position += bytesIn;
i = bytes.Length - 1;
}
}
}
}
// no luck? return to original position
s.Position += soughtBack;
return 0;
}
按照Mark Adler的建议,我尝试了DotNetZip,瞧,它的GZipStream.Position
属性不仅不会抛出,它甚至返回实际读入的gzip字节数(加上8,出于某种原因,我仍然需要弄清楚)。
所以它确实读起来比严格必要的更多,但它让我计算出回溯多少。
以下内容对我有用:
var posBefore = fileStream.Position;
long compressedBytesRead;
using (var gz = new GZipStream(fileStream, CompressionMode.Decompress, true))
{
while (gz.Read(buffer, 0, buffer.Length) != 0)
; // use it!
compressedBytesRead = gz.Position;
}
var gzipStreamAdvance = fileStream.Position - posBefore;
var seekBack = gzipStreamAdvance - compressedBytesRead - 8; // but why "- 8"?
fileStream.Position -= seekBack;