计算压缩级别为 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();
}
我遇到了同样的问题,阅读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();
}