4

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

3 回答 3

8

我遇到了同样的问题,阅读 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做的事情有点不同。

于 2013-10-15T11:55:53.573 回答
2

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

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

于 2012-12-10T14:34:33.483 回答
0

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

这样做的好处是,它应该适用于任何实现(例如 System.IO.Compression 中的那个,它根据文件名编码或文件大小有许多分支)。

重要的部分是使用Stream.Null而不是 a 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(); 
}
于 2017-04-21T15:01:33.993 回答