GZipStream 在大文件上悄悄失败,流以 2GB 结束
GZipStream quietly fails on large file, stream ends at 2GB
我在使用 GZipStream
解压缩流过早结束的 FreebaseRDF dump(30GB 压缩文本,480GB 未压缩)时遇到问题。没有抛出异常,只是 gz.Read()
开始返回零:
using(var gz = new GZipStream(File.Open("freebase-rdf-latest.gz", FileMode.Open), CompressionMode.Decompress))
{
var buffer = new byte[1048576];
int read, total = 0;
while ((read = gz.Read(buffer, 0, buffer.Length)) > 0)
total += read;
// total is 1945715682 here
// subsequent reads return 0
}
该文件可以很好地与其他应用程序一起解压(我尝试了 gzip 和 7zip)。
四处嗅探我在之前的版本GZipStream documentation on MSDN中发现了这个注释:
The GZipStream class might not be able to decompress data that results
in over 8 GB of uncompressed data.
该注释已在最新版本的文档中删除。我使用的是 .NET 4.5.2,对我来说,流在解压不到 2GB 后就结束了。
有人知道这个限制吗?文档中的语言暗示了其他先决条件,而不仅仅是解压超过 8gb 的文件 - 我相当确定我过去使用 GZipStream 来处理非常大的文件而没有遇到这个问题。
此外,任何人都可以推荐我可以用来代替 System.IO.Compression 的 GZipStream 的直接替代品吗?
更新
我尝试用 Ionic.Zlib (DotNetZip) 替换 System.IO.Compression 并得到相同的结果。
我尝试了 ICSharpCode.SharpZipLib 的 GZipInputStream 并在第一次阅读时得到 "unknown block type 6"。
我尝试了 SevenZipSharp,但没有用于读取的流装饰器 - 只有各种阻塞 "Extract" 方法来解压整个流,这不是我想要的。
另一个更新
使用zlib1.dll,下面的代码正确解压了整个文件。它也只用了 GZipStream 的 1/4 左右的时间!
var gzFile = gzopen("freebase-rdf-latest.gz", "rb");
var buffer = new byte[1048576];
int read, total = 0;
while ((read = gzread(gzFile, buffer, buffer.Length)) > 0)
total += read;
[DllImport("zlib1")] IntPtr gzopen(string path, string mode);
[DllImport("zlib1")] int gzread(IntPtr gzFile, byte[] buf, int len);
[DllImport("zlib1")] int gzclose(IntPtr gzFile);
..所以显然.NET 中所有现有的 GZip 库都与 zlib 存在一些兼容性问题。我使用的 zlib1.dll 来自我的 mingw64 目录(我的机器上有大约一打 zlib1.dll 但这是唯一的 64 位目录)。
对于大文件你不应该使用流 reader:
var buffer = new byte[1024 * 1024];
using (var gz = new GZipStream(new FileStream("freebase-rdf-latest.gz", FileMode.Open), CompressionMode.Decompress))
{
var bytesRead = 0;
while (bytesRead < buffer.Length)
{
bytesRead = gz.Read(buffer, 0, buffer.Length);
Console.WriteLine(bytesRead);
}
}
我来晚了一点,但是我已经找到了这个问题的原因和解决方法。
这个大文件不仅包含一个 gzip-stream,而且包含正好 200 个流。 (每个 gzip-stream 的压缩大小:150-155 MB)
首先"gzip-file"使用可选的extra-fields来描述所有压缩的长度gzip-stream。许多解压器不支持这个 streaming-style 并且只解码第一个条目。 (150 MB -> 2 GB)
1.: read-header-method:(对不起,如果看起来像 hacking-style :-)
static long[] ReadGzipLengths(Stream stream)
{
if (!stream.CanSeek || !stream.CanRead) return null; // can seek and read?
int fieldBytes;
if (stream.ReadByte() == 0x1f && stream.ReadByte() == 0x8b // gzip magic-code
&& stream.ReadByte() == 0x08 // deflate-mode
&& stream.ReadByte() == 0x04 // flagged: has extra-field
&& stream.ReadByte() + stream.ReadByte() + stream.ReadByte() + stream.ReadByte() >= 0 // unix timestamp (ignored)
&& stream.ReadByte() == 0x00 // extra-flag: sould be zero
&& stream.ReadByte() >= 0 // OS-Type (ignored)
&& (fieldBytes = stream.ReadByte() + stream.ReadByte() * 256 - 4) > 0 // length of extra-field (subtract 4 bytes field-header)
&& stream.ReadByte() == 0x53 && stream.ReadByte() == 0x5a // field-header: must be "SZ" (mean: gzip-sizes as uint32-values)
&& stream.ReadByte() + stream.ReadByte() * 256 == fieldBytes // should have same length
)
{
var buf = new byte[fieldBytes];
if (stream.Read(buf, 0, fieldBytes) == fieldBytes && fieldBytes % 4 == 0)
{
var result = new long[fieldBytes / 4];
for (int i = 0; i < result.Length; i++) result[i] = BitConverter.ToUInt32(buf, i * sizeof(uint));
stream.Position = 0; // reset stream-position
return result;
}
}
// --- fallback for normal gzip-files or unknown structures ---
stream.Position = 0; // reset stream-position
return new[] { stream.Length }; // return single default-length
}
2.: reader
static void Main(string[] args)
{
using (var fileStream = File.OpenRead(@"freebase-rdf-latest.gz"))
{
long[] gzipLengths = ReadGzipLengths(fileStream);
long gzipOffset = 0;
var buffer = new byte[1048576];
long total = 0;
foreach (long gzipLength in gzipLengths)
{
fileStream.Position = gzipOffset;
using (var gz = new GZipStream(fileStream, CompressionMode.Decompress, true)) // true <- don't close FileStream at Dispose()
{
int read;
while ((read = gz.Read(buffer, 0, buffer.Length)) > 0) total += read;
}
gzipOffset += gzipLength;
Console.WriteLine("Uncompressed Bytes: {0:N0} ({1:N2} %)", total, gzipOffset * 100.0 / fileStream.Length);
}
}
}
3.: 结果
Uncompressed Bytes: 1.945.715.682 (0,47 %)
Uncompressed Bytes: 3.946.888.647 (0,96 %)
Uncompressed Bytes: 5.945.104.284 (1,44 %)
...
...
Uncompressed Bytes: 421.322.787.031 (99,05 %)
Uncompressed Bytes: 423.295.620.069 (99,53 %)
Uncompressed Bytes: 425.229.008.315 (100,00 %)
需要一些时间(30-40 分钟)但它有效! (没有外部库)
速度:约 200 MB/s 解压缩 data-rate
稍作改动,应该可以实现多线程。
我在使用 GZipStream
解压缩流过早结束的 FreebaseRDF dump(30GB 压缩文本,480GB 未压缩)时遇到问题。没有抛出异常,只是 gz.Read()
开始返回零:
using(var gz = new GZipStream(File.Open("freebase-rdf-latest.gz", FileMode.Open), CompressionMode.Decompress))
{
var buffer = new byte[1048576];
int read, total = 0;
while ((read = gz.Read(buffer, 0, buffer.Length)) > 0)
total += read;
// total is 1945715682 here
// subsequent reads return 0
}
该文件可以很好地与其他应用程序一起解压(我尝试了 gzip 和 7zip)。
四处嗅探我在之前的版本GZipStream documentation on MSDN中发现了这个注释:
The GZipStream class might not be able to decompress data that results in over 8 GB of uncompressed data.
该注释已在最新版本的文档中删除。我使用的是 .NET 4.5.2,对我来说,流在解压不到 2GB 后就结束了。
有人知道这个限制吗?文档中的语言暗示了其他先决条件,而不仅仅是解压超过 8gb 的文件 - 我相当确定我过去使用 GZipStream 来处理非常大的文件而没有遇到这个问题。
此外,任何人都可以推荐我可以用来代替 System.IO.Compression 的 GZipStream 的直接替代品吗?
更新
我尝试用 Ionic.Zlib (DotNetZip) 替换 System.IO.Compression 并得到相同的结果。
我尝试了 ICSharpCode.SharpZipLib 的 GZipInputStream 并在第一次阅读时得到 "unknown block type 6"。
我尝试了 SevenZipSharp,但没有用于读取的流装饰器 - 只有各种阻塞 "Extract" 方法来解压整个流,这不是我想要的。
另一个更新
使用zlib1.dll,下面的代码正确解压了整个文件。它也只用了 GZipStream 的 1/4 左右的时间!
var gzFile = gzopen("freebase-rdf-latest.gz", "rb");
var buffer = new byte[1048576];
int read, total = 0;
while ((read = gzread(gzFile, buffer, buffer.Length)) > 0)
total += read;
[DllImport("zlib1")] IntPtr gzopen(string path, string mode);
[DllImport("zlib1")] int gzread(IntPtr gzFile, byte[] buf, int len);
[DllImport("zlib1")] int gzclose(IntPtr gzFile);
..所以显然.NET 中所有现有的 GZip 库都与 zlib 存在一些兼容性问题。我使用的 zlib1.dll 来自我的 mingw64 目录(我的机器上有大约一打 zlib1.dll 但这是唯一的 64 位目录)。
对于大文件你不应该使用流 reader:
var buffer = new byte[1024 * 1024];
using (var gz = new GZipStream(new FileStream("freebase-rdf-latest.gz", FileMode.Open), CompressionMode.Decompress))
{
var bytesRead = 0;
while (bytesRead < buffer.Length)
{
bytesRead = gz.Read(buffer, 0, buffer.Length);
Console.WriteLine(bytesRead);
}
}
我来晚了一点,但是我已经找到了这个问题的原因和解决方法。
这个大文件不仅包含一个 gzip-stream,而且包含正好 200 个流。 (每个 gzip-stream 的压缩大小:150-155 MB)
首先"gzip-file"使用可选的extra-fields来描述所有压缩的长度gzip-stream。许多解压器不支持这个 streaming-style 并且只解码第一个条目。 (150 MB -> 2 GB)
1.: read-header-method:(对不起,如果看起来像 hacking-style :-)
static long[] ReadGzipLengths(Stream stream)
{
if (!stream.CanSeek || !stream.CanRead) return null; // can seek and read?
int fieldBytes;
if (stream.ReadByte() == 0x1f && stream.ReadByte() == 0x8b // gzip magic-code
&& stream.ReadByte() == 0x08 // deflate-mode
&& stream.ReadByte() == 0x04 // flagged: has extra-field
&& stream.ReadByte() + stream.ReadByte() + stream.ReadByte() + stream.ReadByte() >= 0 // unix timestamp (ignored)
&& stream.ReadByte() == 0x00 // extra-flag: sould be zero
&& stream.ReadByte() >= 0 // OS-Type (ignored)
&& (fieldBytes = stream.ReadByte() + stream.ReadByte() * 256 - 4) > 0 // length of extra-field (subtract 4 bytes field-header)
&& stream.ReadByte() == 0x53 && stream.ReadByte() == 0x5a // field-header: must be "SZ" (mean: gzip-sizes as uint32-values)
&& stream.ReadByte() + stream.ReadByte() * 256 == fieldBytes // should have same length
)
{
var buf = new byte[fieldBytes];
if (stream.Read(buf, 0, fieldBytes) == fieldBytes && fieldBytes % 4 == 0)
{
var result = new long[fieldBytes / 4];
for (int i = 0; i < result.Length; i++) result[i] = BitConverter.ToUInt32(buf, i * sizeof(uint));
stream.Position = 0; // reset stream-position
return result;
}
}
// --- fallback for normal gzip-files or unknown structures ---
stream.Position = 0; // reset stream-position
return new[] { stream.Length }; // return single default-length
}
2.: reader
static void Main(string[] args)
{
using (var fileStream = File.OpenRead(@"freebase-rdf-latest.gz"))
{
long[] gzipLengths = ReadGzipLengths(fileStream);
long gzipOffset = 0;
var buffer = new byte[1048576];
long total = 0;
foreach (long gzipLength in gzipLengths)
{
fileStream.Position = gzipOffset;
using (var gz = new GZipStream(fileStream, CompressionMode.Decompress, true)) // true <- don't close FileStream at Dispose()
{
int read;
while ((read = gz.Read(buffer, 0, buffer.Length)) > 0) total += read;
}
gzipOffset += gzipLength;
Console.WriteLine("Uncompressed Bytes: {0:N0} ({1:N2} %)", total, gzipOffset * 100.0 / fileStream.Length);
}
}
}
3.: 结果
Uncompressed Bytes: 1.945.715.682 (0,47 %)
Uncompressed Bytes: 3.946.888.647 (0,96 %)
Uncompressed Bytes: 5.945.104.284 (1,44 %)
...
...
Uncompressed Bytes: 421.322.787.031 (99,05 %)
Uncompressed Bytes: 423.295.620.069 (99,53 %)
Uncompressed Bytes: 425.229.008.315 (100,00 %)
需要一些时间(30-40 分钟)但它有效! (没有外部库)
速度:约 200 MB/s 解压缩 data-rate
稍作改动,应该可以实现多线程。