使用GZipStream计算进度条

3

我正在从一些缓慢的源(如FTP服务器)读取一个.gz文件,并立即处理接收到的数据。看起来像这样:

FtpWebResponse response = ftpclientRequest.GetResponse() as FtpWebResponse;
using (Stream ftpStream = response.GetResponseStream())
using (GZipStream unzipped = new GZipStream(ftpStream, CompressionMode.Decompress))
using (StreamReader linereader = new StreamReader(unzipped))
{
  String l;
  while ((l = linereader.ReadLine()) != null)
  {
    ...
  }
}

我的问题是如何显示一个准确的进度条。我可以提前得到压缩后的 .gz 文件大小,但我不知道解压后的内容有多大。 逐行读取文件时,我知道我读了多少未压缩的字节,但我不知道这与压缩文件大小有什么关系。
那么,有没有办法从 GZipStream 中获取文件指针在压缩文件中前进了多远?我只需要当前位置,gz 文件大小可以在读取文件之前获取。
4个回答

4
您可以在 GZipStream 中间插入一个流,用于计算 GZipStream 已读取的字节数。
  public class ProgressStream : Stream
  {
    public long BytesRead { get; set; }
    Stream _baseStream;
    public ProgressStream(Stream s)
    {
      _baseStream = s;
    }
    public override bool CanRead
    {
      get { return _baseStream.CanRead; }
    }
    public override bool CanSeek
    {
      get { return false; }
    }
    public override bool CanWrite
    {
      get { return false; }
    }
    public override void Flush()
    {
      _baseStream.Flush();
    }
    public override long Length
    {
      get { throw new NotImplementedException(); }
    }
    public override long Position
    {
      get
      {
        throw new NotImplementedException();
      }
      set
      {
        throw new NotImplementedException();
      }
    }
    public override int Read(byte[] buffer, int offset, int count)
    {
      int rc = _baseStream.Read(buffer, offset, count);
      BytesRead += rc;
      return rc;
    }
    public override long Seek(long offset, SeekOrigin origin)
    {
      throw new NotImplementedException();
    }
    public override void SetLength(long value)
    {
      throw new NotImplementedException();
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
      throw new NotImplementedException();
    }
  }

// usage
FtpWebResponse response = ftpclientRequest.GetResponse() as FtpWebResponse;
using (Stream ftpStream = response.GetResponseStream())
using (ProgressStream progressStream = new ProgressStream(ftpstream))
using (GZipStream unzipped = new GZipStream(progressStream, CompressionMode.Decompress))
using (StreamReader linereader = new StreamReader(unzipped))
{
  String l;
  while ((l = linereader.ReadLine()) != null)
  {
    progressStream.BytesRead(); // does contain the # of bytes read from FTP so far.
  }
}

太好了,这正是我要找的!可惜 Ftp-Stream 无法支持返回已读取字节! - Sam

0
作为代理解压缩进度,您可以尝试使用以下方法从底层流中获取文件下载进度的信息:
var percentageProgress = ftpStream.Position / (double)ftpWebResponse.ContentLength;

或者

var percentageProgress = ftpStream.Position / (double)ftpStream.Length;

它适用于 FileStream,只要它实现了 Position 属性并且 FTP 服务器返回已下载文件的长度信息,它应该也适用于 GetResponseStream()http://msdn.microsoft.com/en-us/library/system.net.ftpwebresponse.contentlength(v=vs.110).aspx


0
我建议您看一下以下代码:
public static readonly byte[] symbols = new byte[8 * 1024];

public static void Decompress(FileInfo inFile, FileInfo outFile)
{
    using (var inStream = inFile.OpenRead())
    {
        using (var zipStream = new GZipStream(inStream, CompressionMode.Decompress))
        {
            using (var outStream = outFile.OpenWrite())
            {
                var total = 0;
                do
                {
                    var async = zipStream.BeginRead(symbols, 0, symbols.Length, null, null);
                    total = zipStream.EndRead(async);
                    if (total != 0)
                    {
                        // Report progress. Read total bytes (8K) from the zipped file.
                        outStream.Write(symbols, 0, total);
                    }
                } while (total != 0);
            }
        }
    }
}

过度和不必要地使用var关键字会使代码变得非常难读。 - Oliver Friedrich
“var” 确实让我们更容易地输入一个例子,然后让编译器处理它。 - kenny
很抱歉,我不明白这如何帮助我计算我在gz文件中的进度。'total'包含未压缩的进度,这对我没有帮助,因为我不知道未压缩时文件有多大。我需要知道我的压缩字节位置。 - Sam

0

我重新审查了我的代码并进行了一些测试。在我看来,darin是正确的。但是我认为可以仅读取压缩流的标题(大小?)并找出结果文件的大小。(WinRar“知道”未解压缩整个zip存档的情况下未压缩文件的大小。它从存档的标题中读取此信息。)如果您找到了结果文件的大小,则此代码将帮助您报告精确的进度。

public static readonly byte[] symbols = new byte[8 * 1024];

public static void Decompress(FileInfo inFile, FileInfo outFile, double size, Action<double> progress)
{
    var percents = new List<double>(100);

    using (var inStream = inFile.OpenRead())
    {
        using (var zipStream = new GZipStream(inStream, CompressionMode.Decompress))
        {
            using (var outStream = outFile.OpenWrite())
            {
                var current = 0;

                var total = 0;
                while ((total = zipStream.Read(symbols, 0, symbols.Length)) != 0)
                {
                    outStream.Write(symbols, 0, total);
                    current += total;

                    var p = Math.Round(((double)current / size), 2) * 100;
                    if (!percents.Contains(p))
                    {
                        if (progress != null)
                        {
                            progress(p);
                        }
                        percents.Add(p);
                    }
                }
            }
        }
    }
}

希望这能有所帮助。


Petar,就像你第一个例子中未压缩文件的当前位置是正确的一样,但对我来说没有用,因为我不知道未压缩文件的大小。我认为GZip不像Rar那样存储文件大小,所以我无法获取未压缩大小。 - Sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接