3

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

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

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

谢谢,马诺伊

4

4 回答 4

5

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

不要忘记,除了 的当前大小之外MemoryStream,任何时候它必须增长,您最终都会(暂时)同时在内存中使用新数组旧数组。当然,如果您事先知道内容长度,那将不是问题,但是将其写入磁盘并在进行时进行散列仍然会更好。

于 2009-10-02T20:30:04.070 回答
4

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

于 2009-10-02T20:35:40.363 回答
2

Afaik CLR 托管堆不会分配大于 2 GB 的任何内容,并且 MemoryStream 由实时、连续的字节 [] 支持。大对象堆不处理超过 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();
        }

    }
}
于 2009-10-02T20:46:28.703 回答
0

我很确定你会得到 OutOfMemoryException。简单的尝试方法是尝试使用内存流将 DVD ISO 映像或其他内容读入内存。如果你能读懂全文,那你应该没问题。如果你得到一个例外,那么,你去。

于 2009-10-02T20:28:46.927 回答