【问题标题】:Will a large System.IO.MemoryStream result in my application's memory usage increasing dramatically?大型 System.IO.MemoryStream 会导致我的应用程序的内存使用量急剧增加吗?
【发布时间】:2009-10-02 20:24:31
【问题描述】:

我正在构建一个允许用户从 URL 下载文件的库。我正在考虑的选项之一是让用户为文件指定预期的 MD5 校验和;库的 GetFile(string url) 函数确保下载流的校验和与用户指定的校验和匹配。

意识到 HttpWebResponse.GetResponseStream() 返回的 NetworkStream 不可搜索,我找到了一种复制 Stream 的方法,这要归功于这个问题的答案:How can I read an Http response stream twice in C#?。不过,在我走得更远之前,我想弄清楚这种重复对记忆的影响是什么;不幸的是,谷歌和 MSDN 上的多次搜索都失败了。

库对要下载的文件大小没有限制。我的问题是,如果用户选择一个 2GB 的文件,.NET 2.0 中的 MemoryStream 实现是否足够聪明,可以有效地使用 PageFile 和 RAM,以至于系统不会因为 VM 崩溃而开始爬网?此外,Jon Skeet 对另一个问题的评论给了我一些思考 - 他断言即使在处理 MemoryStream 之后,内存也不是 100% 释放的。如何以及何时可以确保实际释放内存?会根据系统的要求(和必要性)发布吗?

谢谢, 马诺吉

【问题讨论】:

  • 当他说它不是 100% 释放时,他指的是自动垃圾收集吗?
  • 他很可能是。你可以调用 GC.Collect() 来手动强制垃圾回收,但这是一个不好的习惯,特别是如果调用它的方法会被频繁调用。
  • 我的意思是,如果你在调用 Dispose 后保持MemoryStream,数据仍然存在 - 如果你调用ToArray,你仍然可以得到数据。在MemoryStream 之前,该数组不符合收集条件。
  • 换句话说,在 MemoryStream 上调用 Dispose 以使内存可供 GC 使用是愚蠢的差事 :)

标签: c# httpwebresponse memorystream


【解决方案1】:

您正在将其保存到文件中,对吗?为什么不逐块保存它,随时更新哈希,然后只检查最后的哈希?我认为您不需要两次阅读响应,也不需要缓冲它。正如另一个答案指出的那样,无论如何,当你超过 1GB 时,它就会失败。

不要忘记,除了MemoryStream 的当前大小,任何时候它必须增长,您最终都会(暂时)使用新数组加上旧数组同时记忆。当然,如果您事先知道内容长度,那当然不会有问题,但最好将其写入磁盘并随时进行哈希处理。

【讨论】:

  • 不将流保存到文件;流被传递给用户以执行他们认为适合数据的任何操作。计算哈希的选项是提供给用户的,但从手头任务的复杂性来看,我将重新考虑将此功能添加到库中。谢谢!
【解决方案2】:

MemoryStream 由数组支持。即使你有一个 64 位操作系统,这也不会超过 1GB,因为框架不会分配更大的数组。

【讨论】:

    【解决方案3】:

    Afaik CLR 托管堆不会分配大于 2 GB 的任何内容,并且 MemoryStream 由实时、连续的字节 [] 支持。 Large Object Heap 不处理超过 2GB 的分配,甚至在 x64 上也不处理。

    但是将整个文件存储在内存中只是为了计算哈希似乎技术含量很低。您可以在接收字节时逐块计算哈希。每次 IO 完成后,您可以对接收到的字节进行哈希处理,然后将写入提交到文件。最后,您已经计算出散列上传的文件,呵呵。

    顺便说一句,如果您寻求代码来操作文件,请避开任何包含 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();
            }
    
        }
    }
    

    【讨论】:

    • 我无权将流的内容写入磁盘(即使是暂时的),因为该库无权将数据写入用户的文件系统。
    • 那我不明白。您说您的组件下载文件。它如何在不写入磁盘的情况下下载它?您是说要让浏览器处理下载,但又要以某种方式获取 md5 校验和的下载流?还是要下载两次,一次由浏览器下载一次,一次由您的校验和代码下载?要计算校验和,您不必将其写入任何地方,只需在每个缓冲区上调用 ComputeHash,然后丢弃缓冲区。
    • 为了从网络URI中获取对象,库提供了如下接口: Stream GetObject(string uri);正在考虑的选项之一是使用一个标志重载 GetObject,该标志指定代表用户为流计算 MD5 摘要: Stream GetObject(string uri, bool fVerifyDigest);网络文件服务器为正在下载的对象提供 MD5 摘要作为 HTTPWebResponse 标头之一。这个想法是计算流的哈希值并将其与服务器返回的值进行比较。有意义吗?
    • 我明白了。您应该返回您自己的从 Stream 派生的类,该类实现了这一点(计算散列)。类似于 DeflateStream、GZipStream 或 CryptoStream 以及其他 Stream 派生类的工作方式。您构建您的 Stream 来包装 HttpResult 流。您覆盖 Read 并在实现中从 http 流中请求字节,将它们添加到哈希中,然后将它们返回给调用者。
    【解决方案4】:

    我很确定您会收到 OutOfMemoryException。简单的尝试方法是尝试使用内存流将 DVD ISO 映像或其他内容读入内存。如果你能读懂全文,那你应该没问题。如果您遇到异常,那么您就可以了。

    【讨论】:

      猜你喜欢
      • 2019-04-25
      • 1970-01-01
      • 1970-01-01
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-24
      相关资源
      最近更新 更多