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

本文关键字:zip 文件 压缩 计算 | 更新日期: 2023-09-27 18:36:29

我正在为我们的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();
}

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

我遇到了同样的问题,阅读ZIP规范提出了以下解决方案:

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做一些不同的事情。

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

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

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

这样做的好处是,它应该适用于任何实现(例如来自System.IO.Compressing的实现,它有许多分支,具体取决于文件名编码或文件大小)。

重要的部分是使用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 是一个简单的 Wrapper,它只跟踪位置:

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(); 
}