计算压缩级别为0的zip文件的大小

时间:2012-06-07 07:27:42

标签: c# zip

我正在为我们的Web服务器编写功能,该服务器应该从其他服务器下载多个文件,并将它们作为zip存档返回而不进行压缩。

如果我知道所有下载文件的大小,如何确定ZIP存档的最终大小?

这是我目前正在处理的代码。注释行导致ZIP存档损坏。

public void Download()
{
    var urls = Request.Headers["URLS"].Split(';');
    Task<WebResponse>[] responseTasks = urls
        .Select(it =>
        {
            var request = WebRequest.Create(it);
            return Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse(null, null), request.EndGetResponse);
        })
        .ToArray();

    Task.WaitAll(responseTasks);

    var webResponses = responseTasks.Where(it => it.Exception == null).Select(it => it.Result);

    var totalSize = webResponses.Sum(it => it.ContentLength + 32);

    Response.ContentType = "application/zip";
    Response.CacheControl = "Private";
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
    // Response.AddHeader("Content-Length", totalSize.ToString(CultureInfo.InvariantCulture));

    var sortedResponses = webResponses.OrderBy(it => it.ContentLength);

    var buffer = new byte[32 * 1024];

    using (var zipOutput = new ZipOutputStream(Response.OutputStream))
    {
        zipOutput.SetLevel(0);

        foreach (var response in sortedResponses)
        {
            var dataStream = response.GetResponseStream();

            var ze = new ZipEntry(Guid.NewGuid().ToString() + ".jpg");
            zipOutput.PutNextEntry(ze);

            int read;
            while ((read = dataStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                zipOutput.Write(buffer, 0, read);
                Response.Flush();
            }

            if (!Response.IsClientConnected)
            {
                break;
            }
        }

        zipOutput.Finish();
    }

    Response.Flush();
    Response.End();
}

3 个答案:

答案 0 :(得分:6)

我遇到了同样的问题,并且阅读了ZIP-spec,提出了以下解决方案:

zip_size = num_of_files * (30 + 16 + 46) + 2 * total_length_of_filenames + total_size_of_files + 22

使用:

  • 30:修复了Local file header
  • 的一部分
  • 16:可选:Data descriptor
  • 的大小
  • 46:修复了Central directory file header
  • 的一部分
  • 22:修复了End of central directory record (EOCD)
  • 的部分内容

然而,这并未考虑对文件和zip的总体评论。压缩存储(级别0)。

这适用于我编写的ZIP实现。正如nickolay-olshevsky指出的那样,其他压缩器might做的事情略有不同。

答案 1 :(得分:2)

ZIP文件由一些每个文件记录和一些每个归档记录组成。 它们具有复杂的结构,并且可以根据所使用的存档器而在尺寸上不同。 但是,如果您将使用具有相同压缩选项的相同实现,则归档大小将仅取决于输入的大小和输入文件名的大小。

因此,您可以使用1和2文件进行存档,并且知道它们的大小,输入文件大小,文件名大小,计算每个存档的有效负载大小,每个文件的有效负载大小,以及存档大小的依赖性。文件名(文件名在两个地方使用)。

答案 2 :(得分:0)

我遇到了同样的问题,最终创建了一个假档案并跟踪大小。

这有一个优点,它应该适用于任何实现(例如System.IO.Compression中的一个实现,它具有许多分支,具体取决于文件名编码或文件大小)。

重要的部分是使用Stream.Null而不是MemoryStream,因此没有内存用于计算。

public long Size(FileItem[] files)
{
    using (var ms = new PositionWrapperStream(Stream.Null))
    {
        using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true))
        {
            foreach (var file in files)
            {
                var entry = archive.CreateEntry(file.Name, CompressionLevel.NoCompression);
                using (var zipStream = entry.Open())
                {
                    WriteZero(zipStream, file.Length);//the actual content does not matter
                }
            }
        }
        return ms.Position;
    }
}

private void WriteZero(Stream target, long count)
{
    byte[] buffer = new byte[1024];
    while (count > 0)
    {
        target.Write(buffer, 0, (int) Math.Min(buffer.Length, count));
        count -= buffer.Length;
    }
}

PositionWrapperStream是一个简单的包装器,只跟踪位置:

class PositionWrapperStream : Stream
{
    private readonly Stream wrapped;

    private int pos = 0;

    public PositionWrapperStream(Stream wrapped)
    {
        this.wrapped = wrapped;
    }

    public override bool CanSeek { get { return false; } }

    public override bool CanWrite { get { return true; } }

    public override long Position
    {
        get { return pos; }
        set { throw new NotSupportedException(); }
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        pos += count;
        wrapped.Write(buffer, offset, count);
    }

    //...other methods with throw new NotSupportedException(); 
}