我正在为我们的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 :(得分:6)
我遇到了同样的问题,并且阅读了ZIP-spec,提出了以下解决方案:
zip_size = num_of_files * (30 + 16 + 46) + 2 * total_length_of_filenames + total_size_of_files + 22
使用:
Local file header
Data descriptor
Central directory file header
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();
}