2009-10-02 40 views
3

我正在構建一個允許用戶從URL下載文件的庫。我正在考慮的其中一個選項是讓用戶爲文件指定預期的MD5校驗和;庫的GetFile(string url)函數確保下載的流的校驗和與用戶指定的校驗和相匹配。大型System.IO.MemoryStream是否會導致應用程序的內存使用量急劇增加?

由於知道由HttpWebResponse.GetResponseStream()返回的NetworkStream不可搜索,所以我找到了一種方法來複制Stream,感謝這個問題的答案:How can I read an Http response stream twice in C#?。在我走得更遠之前,我想弄清楚這種重複的內存影響是什麼;不幸的是,Google和MSDN上的多次搜索已經失效。

該庫對要下載的文件的大小沒有限制。我的問題是,如果用戶選擇2GB文件,那麼.NET 2.0中的MemoryStream實現足夠智能,足夠高效地使用PageFile和RAM,以至於系統不會因VM緊縮而開始爬網?另外,Jon Skeet對另一個問題的評論給了我一些想法 - 他斷言即使在處置MemoryStream之後,內存也不是100%免費的。如何以及何時可以確保內存實際上被釋放?它會根據系統的要求(和必要性)發佈嗎?

感謝, 馬諾

+0

當他說這是不是100%中解脫出來,被他指的是自動垃圾採集? – 2009-10-02 20:26:48

+0

他很可能是。你可以打電話給GC。Collect()手動強制垃圾收集,但這是一個不好的習慣,特別是如果調用它的方法會被頻繁調用。 – Chris 2009-10-02 20:29:33

+0

我的意思是,如果你在調用Dispose之後堅持一個'MemoryStream',那麼數據仍然存在 - 如果你調用'ToArray',你仍然可以獲取數據。在MemoryStream存儲之前,該數組不適用於收集。 – 2009-10-02 20:34:02

回答

0

我敢肯定你會得到一個OutOfMemoryException。簡單的嘗試方法是嘗試使用內存流將DVD ISO映像或其他內容讀取到內存中。如果你能讀完整件事,那麼你應該沒問題。如果你遇到異常,那麼你就去。

5

你將它保存到一個文件,對吧?爲什麼不按塊大塊地保存塊,隨時更新散列,然後在最後檢查散列?我認爲你不需要兩次讀取響應,也不需要緩衝它。正如另一個答案指出的那樣,當你超過1GB時,這會失敗。

不要忘了,還有MemoryStream的電流的大小,它必須成長,你會與(臨時)新陣列加上同時舊的陣列中的存儲結束的任何時間。當然,如果您事先知道內容長度,那麼這不會成爲問題,但將它寫入磁盤和散列時仍然更好。

+0

不保存流到文件;該數據流將傳遞給用戶以執行他們認爲適合數據的任何操作。計算散列的選項作爲一個很好的提供給用戶,但從手頭任務的複雜性來看,我將重新考慮將此功能添加到庫中。謝謝! – user152771 2009-10-05 19:24:16

4

MemoryStream由數組支持。即使你有一個64位的操作系統,這也不會超過1GB,因爲框架不會分配更大的數組。

2

Afaik CLR託管堆將不會分配大於2 GB的任何內容,並且MemoryStream由活動的連續字節[]支持。 Large Object Heap的分配句柄不超過2GB,即使在x64上也沒有。

但是要將整個文件存儲在內存中只是爲了計算散列似乎是相當低的技術。您可以在收到字節時按塊計算哈希。在每個IO完成後,您可以散列接收到的字節,然後將寫入提交給文件。最後,你已經計算了這個文件上傳的huraay。

順便說一句,如果你想尋找的代碼文件進行操作,避開一個包含詞語ReadToEnd任何樣品的...

class Program 
    { 
     private static AutoResetEvent done = new AutoResetEvent(false); 
     private static AsyncCallback _callbackReadStream; 
     private static AsyncCallback _callbackWriteFile; 

     static void Main(string[] args) 
     { 

     try 
     { 
      _callbackReadStream = new AsyncCallback(CallbackReadStream); 
      _callbackWriteFile = new AsyncCallback(CallbackWriteFile); 
      string url = "http://..."; 
      WebRequest request = WebRequest.Create(url); 
      request.Method = "GET"; 
      request.BeginGetResponse(new AsyncCallback(
       CallbackGetResponse), request); 
      done.WaitOne(); 
     } 
     catch (Exception e) 
     { 
      Console.Error.WriteLine(e.Message); 
     } 
    } 

    private class State 
    { 
     public Stream ReponseStream { get; set; } 
     public HashAlgorithm Hash { get; set; } 
     public Stream FileStream { get; set; } 
     private byte[] _buffer = new byte[16379]; 
     public byte[] Buffer { get { return _buffer; } } 
     public int ReadBytes { get; set; } 
     public long FileLength {get;set;} 
    } 

    static void CallbackGetResponse(IAsyncResult ar) 
    { 
     try 
     { 
      WebRequest request = (WebRequest)ar.AsyncState; 
      WebResponse response = request.EndGetResponse(ar); 

      State s = new State(); 
      s.ReponseStream = response.GetResponseStream(); 
      s.FileStream = new FileStream("download.out" 
       , FileMode.Create 
       , FileAccess.Write 
       , FileShare.None); 
      s.Hash = HashAlgorithm.Create("MD5"); 

      s.ReponseStream.BeginRead(
       s.Buffer 
       , 0 
       , s.Buffer.Length 
       , _callbackReadStream 
       , s); 
     } 
     catch (Exception e) 
     { 
      Console.Error.WriteLine(e.Message); 
      done.Set(); 
     } 
    } 

    private static void CallbackReadStream(IAsyncResult ar) 
    { 
     try 
     { 
      State s = (State)ar.AsyncState; 
      s.ReadBytes = s.ReponseStream.EndRead(ar); 
      s.Hash.ComputeHash(s.Buffer, 0, s.ReadBytes); 
      s.FileStream.BeginWrite(
       s.Buffer 
       , 0 
       , s.ReadBytes 
       , _callbackWriteFile 
       , s); 
     } 
     catch (Exception e) 
     { 
      Console.Error.WriteLine(e.Message); 
      done.Set(); 
     } 
    } 

    static private void CallbackWriteFile(IAsyncResult ar) 
    { 
     try 
     { 
      State s = (State)ar.AsyncState; 
      s.FileStream.EndWrite(ar); 

      s.FileLength += s.ReadBytes; 

      if (0 != s.ReadBytes) 
      { 
       s.ReponseStream.BeginRead(
        s.Buffer 
        , 0 
        , s.Buffer.Length 
        , _callbackReadStream 
        , s); 
      } 
      else 
      { 
       Console.Out.Write("Downloaded {0} bytes. Hash(base64):{1}", 
        s.FileLength, Convert.ToBase64String(s.Hash.Hash)); 
       done.Set(); 
      } 
     } 
     catch (Exception e) 
     { 
      Console.Error.WriteLine(e.Message); 
      done.Set(); 
     } 

    } 
} 
+0

我無權將流的內容寫入磁盤(即使是暫時的),因爲磁帶庫未被授權將數據寫入用戶的文件系統。 – user152771 2009-10-05 19:22:31

+0

我不明白。你說你的組件下載文件。如何在不寫入磁盤的情況下下載它?你是說你想讓瀏覽器處理下載,但是爲了獲得md5 checksum的下載流嗎?或者你想要下載它兩次,一次由你的代碼進行校驗和瀏覽器?要計算校驗和,您不必將它寫入任何位置,只需在每個緩衝區中調用ComputeHash,然後放棄緩衝區。 – 2009-10-05 19:35:51

+0

爲了從網絡URI中檢索對象,庫提供以下接口: Stream GetObject(string uri); 正在考慮的其中一個選項是使用一個標誌來重載GetObject,該標誌指定爲用戶的流計算MD5摘要: Stream GetObject(string uri,bool fVerifyDigest); 網絡文件服務器爲正在下載的對象提供MD5摘要作爲HTTPWebResponse頭之一。這個想法是計算流的散列並將其與服務器返回的值進行比較。合理? – user152771 2009-10-05 23:10:00

相關問題