在 dotNET C# 中使用 GZipStream 的正确方法
Correct way to use GZipStream in dotNET C#
我目前正在使用 .net 3.5 使用 GZipStream。
我有下面列出的两种方法。作为输入文件,我使用由字符 's' 组成的文本文件。文件大小为 2MB。如果我使用 .net 4.5,此代码工作正常,但在压缩和解压缩后使用 .net 3.5,我得到大小为 435KB 的文件,这当然与源文件不同。
如果我尝试通过 WinRAR 解压缩文件,它看起来也不错(与源文件相同)。
如果我尝试使用 .net4.5 中的 GZipStream 解压缩文件(通过 .net 3.5 中的 GZipStream 压缩的文件),结果很糟糕。
更新:
一般来说,我真的需要将文件作为几个单独的 gzip 块来读取,在这种情况下,压缩文件的所有字节都是在一次调用 Read() 方法时读取的,所以我仍然不明白为什么解压缩不起作用。
public void CompressFile()
{
string fileIn = @"D:\sin2.txt";
string fileOut = @"D:\sin2.txt.pgz";
using (var fout = File.Create(fileOut))
{
using (var fin = File.OpenRead(fileIn))
{
using (var zip = new GZipStream(fout, CompressionMode.Compress))
{
var buffer = new byte[1024 * 1024 * 10];
int n = fin.Read(buffer, 0, buffer.Length);
zip.Write(buffer, 0, n);
}
}
}
}
public void DecompressFile()
{
string fileIn = @"D:\sin2.txt.pgz";
string fileOut = @"D:\sin2.1.txt";
using (var fsout = File.Create(fileOut))
{
using (var fsIn = File.OpenRead(fileIn))
{
var buffer = new byte[1024 * 1024 * 10];
int n;
while ((n = fsIn.Read(buffer, 0, buffer.Length)) > 0)
{
using (var ms = new MemoryStream(buffer, 0, n))
{
using (var zip = new GZipStream(ms, CompressionMode.Decompress))
{
int nRead = zip.Read(buffer, 0, buffer.Length);
fsout.Write(buffer, 0, nRead);
}
}
}
}
}
}
您正在尝试将每个 "chunk" 解压为一个单独的 gzip 文件。不要那样做 - 只需循环读取 GZipStream
:
using (var fsout = File.Create(fileOut))
{
using (var fsIn = File.OpenRead(fileIn))
{
using (var zip = new GZipStream(fsIn, CompressionMode.Decompress))
{
var buffer = new byte[1024 * 32];
int bytesRead;
while ((bytesRead = zip.Read(buffer, 0, buffer.Length)) > 0)
{
fsout.Write(buffer, 0, bytesRead);
}
}
}
}
请注意,您的压缩代码应该看起来相似,循环读取而不是假设对 Read
的单次调用将读取所有数据。
(我个人会跳过 fsIn
,只使用 new GZipStream(File.OpenRead(fileIn))
,但这只是个人喜好。)
首先,正如@Jon Skeet 提到的,您没有正确使用 Stream.Read
方法。无论您的缓冲区是否足够大都没有关系,允许流比请求的字节少 return 字节,零表示不多,因此从流中读取应该始终在循环中执行。
然而,解压缩代码中的主要问题是共享缓冲区的方式。您将输入读入缓冲区,而不是 wrap 它在 MemoryStream
中(请注意,使用的构造函数不会复制传递的数组,但实际上将其设置为内部缓冲区),然后您尝试同时读取和写入该缓冲区。考虑到解压缩写入数据 "faster" 而不是读取,令人惊讶的是您的代码完全有效。
正确的实现很简单
static void CompressFile()
{
string fileIn = @"D:\sin2.txt";
string fileOut = @"D:\sin2.txt.pgz";
using (var input = File.OpenRead(fileIn))
using (var output = new GZipStream(File.Create(fileOut), CompressionMode.Compress))
Write(input, output);
}
static void DecompressFile()
{
string fileIn = @"D:\sin2.txt.pgz";
string fileOut = @"D:\sin2.1.txt";
using (var input = new GZipStream(File.OpenRead(fileIn), CompressionMode.Decompress))
using (var output = File.Create(fileOut))
Write(input, output);
}
static void Write(Stream input, Stream output, int bufferSize = 10 * 1024 * 1024)
{
var buffer = new byte[bufferSize];
for (int readCount; (readCount = input.Read(buffer, 0, buffer.Length)) > 0;)
output.Write(buffer, 0, readCount);
}
我目前正在使用 .net 3.5 使用 GZipStream。 我有下面列出的两种方法。作为输入文件,我使用由字符 's' 组成的文本文件。文件大小为 2MB。如果我使用 .net 4.5,此代码工作正常,但在压缩和解压缩后使用 .net 3.5,我得到大小为 435KB 的文件,这当然与源文件不同。 如果我尝试通过 WinRAR 解压缩文件,它看起来也不错(与源文件相同)。 如果我尝试使用 .net4.5 中的 GZipStream 解压缩文件(通过 .net 3.5 中的 GZipStream 压缩的文件),结果很糟糕。
更新: 一般来说,我真的需要将文件作为几个单独的 gzip 块来读取,在这种情况下,压缩文件的所有字节都是在一次调用 Read() 方法时读取的,所以我仍然不明白为什么解压缩不起作用。
public void CompressFile()
{
string fileIn = @"D:\sin2.txt";
string fileOut = @"D:\sin2.txt.pgz";
using (var fout = File.Create(fileOut))
{
using (var fin = File.OpenRead(fileIn))
{
using (var zip = new GZipStream(fout, CompressionMode.Compress))
{
var buffer = new byte[1024 * 1024 * 10];
int n = fin.Read(buffer, 0, buffer.Length);
zip.Write(buffer, 0, n);
}
}
}
}
public void DecompressFile()
{
string fileIn = @"D:\sin2.txt.pgz";
string fileOut = @"D:\sin2.1.txt";
using (var fsout = File.Create(fileOut))
{
using (var fsIn = File.OpenRead(fileIn))
{
var buffer = new byte[1024 * 1024 * 10];
int n;
while ((n = fsIn.Read(buffer, 0, buffer.Length)) > 0)
{
using (var ms = new MemoryStream(buffer, 0, n))
{
using (var zip = new GZipStream(ms, CompressionMode.Decompress))
{
int nRead = zip.Read(buffer, 0, buffer.Length);
fsout.Write(buffer, 0, nRead);
}
}
}
}
}
}
您正在尝试将每个 "chunk" 解压为一个单独的 gzip 文件。不要那样做 - 只需循环读取 GZipStream
:
using (var fsout = File.Create(fileOut))
{
using (var fsIn = File.OpenRead(fileIn))
{
using (var zip = new GZipStream(fsIn, CompressionMode.Decompress))
{
var buffer = new byte[1024 * 32];
int bytesRead;
while ((bytesRead = zip.Read(buffer, 0, buffer.Length)) > 0)
{
fsout.Write(buffer, 0, bytesRead);
}
}
}
}
请注意,您的压缩代码应该看起来相似,循环读取而不是假设对 Read
的单次调用将读取所有数据。
(我个人会跳过 fsIn
,只使用 new GZipStream(File.OpenRead(fileIn))
,但这只是个人喜好。)
首先,正如@Jon Skeet 提到的,您没有正确使用 Stream.Read
方法。无论您的缓冲区是否足够大都没有关系,允许流比请求的字节少 return 字节,零表示不多,因此从流中读取应该始终在循环中执行。
然而,解压缩代码中的主要问题是共享缓冲区的方式。您将输入读入缓冲区,而不是 wrap 它在 MemoryStream
中(请注意,使用的构造函数不会复制传递的数组,但实际上将其设置为内部缓冲区),然后您尝试同时读取和写入该缓冲区。考虑到解压缩写入数据 "faster" 而不是读取,令人惊讶的是您的代码完全有效。
正确的实现很简单
static void CompressFile()
{
string fileIn = @"D:\sin2.txt";
string fileOut = @"D:\sin2.txt.pgz";
using (var input = File.OpenRead(fileIn))
using (var output = new GZipStream(File.Create(fileOut), CompressionMode.Compress))
Write(input, output);
}
static void DecompressFile()
{
string fileIn = @"D:\sin2.txt.pgz";
string fileOut = @"D:\sin2.1.txt";
using (var input = new GZipStream(File.OpenRead(fileIn), CompressionMode.Decompress))
using (var output = File.Create(fileOut))
Write(input, output);
}
static void Write(Stream input, Stream output, int bufferSize = 10 * 1024 * 1024)
{
var buffer = new byte[bufferSize];
for (int readCount; (readCount = input.Read(buffer, 0, buffer.Length)) > 0;)
output.Write(buffer, 0, readCount);
}