2012-06-07 48 views
4

我正在爲我們的Web服務器編寫功能,該服務器應該從其他服務器下載幾個文件,並將它們作爲壓縮歸檔文件返回而不進行壓縮。計算壓縮級別爲0的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

最簡單的方法可能是隻創建一個內存流中的ZIP和檢查長度,在複製之前到響應流。 –

+1

ps:你的代碼沒有使用DotnetZip接口。它可能是SharpZipLib。 – Cheeso

回答

2

ZIP文件由一些每個文件記錄和一些每個檔案記錄組成。 他們有複雜的結構,並可以在大小上有所不同,取決於所使用的存檔。 但是,如果您使用相同的壓縮選項使用相同的實現,則您的存檔大小將僅取決於輸入大小和輸入文件名的大小。

因此,您可以使用1和2個文件進行存檔,並瞭解其大小,以及輸入文件大小和文件名大小,計算每個存檔的有效負載大小,每個文件的有效負載大小以及從文件名(文件名在兩個地方使用)。

5

我有同樣的問題和閱讀的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做的事情有點不同。

+0

非常感謝您的幫助,您爲我節省了幾個小時的規格閱讀和實驗! – jbaiter

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