24

我需要在 C# 控制台应用程序中通过 HTTP 下载一个文件 (2 GB)。问题是,大约 1.2 GB 后,应用程序内存不足。

这是我正在使用的代码:

WebClient request = new WebClient();
request.Credentials = new NetworkCredential(username, password);
byte[] fileData = request.DownloadData(baseURL + fName);

如您所见...我正在将文件直接读入内存。如果我要从 HTTP 中读取数据并将其写入磁盘上的文件,我很确定我可以解决这个问题。

我怎么能这样做?

4

6 回答 6

39

如果您使用WebClient.DownloadFile,您可以将其直接保存到文件中。

于 2009-07-03T09:25:00.593 回答
37

WebClient 类是用于简化场景的类。一旦您通过了简单的场景(并且您已经完成了),您将不得不退后一点并使用 WebRequest。

使用 WebRequest,您可以访问响应流,并且可以循环访问它,读取一点并写入一点,直到完成。

从微软文档:

我们不建议您使用 WebRequest 或其派生类进行新开发。而是使用System.Net.Http.HttpClient类。

来源:docs.microsoft.com/WebRequest


例子:

public void MyDownloadFile(Uri url, string outputFilePath)
{
    const int BUFFER_SIZE = 16 * 1024;
    using (var outputFileStream = File.Create(outputFilePath, BUFFER_SIZE))
    {
        var req = WebRequest.Create(url);
        using (var response = req.GetResponse())
        {
            using (var responseStream = response.GetResponseStream())
            {
                var buffer = new byte[BUFFER_SIZE];
                int bytesRead;
                do
                {
                    bytesRead = responseStream.Read(buffer, 0, BUFFER_SIZE);
                    outputFileStream.Write(buffer, 0, bytesRead);
                } while (bytesRead > 0);
            }
        }
    }
}

请注意,如果 WebClient.DownloadFile 有效,那么我将其称为最佳解决方案。我在发布“DownloadFile”答案之前写了上面的内容。我也写得太早了,所以可能需要一粒盐(和测试)。

于 2009-07-03T09:21:51.210 回答
9

您需要获取响应流,然后读取块,将每个块写入文件以允许重用内存。

如您所写,整个响应(全部为 2GB)需要在内存中。即使在 64 位系统上,单个 .NET 对象也会达到 2GB 的限制。


更新:更简单的选择。开始为您WebClient完成工作:使用其DownloadFile将数据直接放入文件的方法。

于 2009-07-03T09:21:33.333 回答
3

WebClient.OpenRead 返回一个 Stream,只需使用 Read 循环遍历内容,因此数据不会缓冲在内存中,而是可以以块的形式写入文件。

于 2013-01-25T09:36:30.990 回答
2

我会用这样的东西

于 2009-07-03T09:30:11.470 回答
0

连接可能会中断,因此最好以小块下载文件。

Akka 流可以帮助使用多线程从 System.IO.Stream 下载小块文件。https://getakka.net/articles/intro/what-is-akka.html

Download 方法会将字节追加到以 long fileStart 开头的文件中。如果文件不存在,fileStart 值必须为 0。

using Akka.Actor;
using Akka.IO;
using Akka.Streams;
using Akka.Streams.Dsl;
using Akka.Streams.IO;

private static Sink<ByteString, Task<IOResult>> FileSink(string filename)
{
    return Flow.Create<ByteString>()
        .ToMaterialized(FileIO.ToFile(new FileInfo(filename), FileMode.Append), Keep.Right);
}

private async Task Download(string path, Uri uri, long fileStart)
{
    using (var system = ActorSystem.Create("system"))
    using (var materializer = system.Materializer())
    {
       HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
       request.AddRange(fileStart);

       using (WebResponse response = request.GetResponse())
       {
           Stream stream = response.GetResponseStream();

           await StreamConverters.FromInputStream(() => stream, chunkSize: 1024)
               .RunWith(FileSink(path), materializer);
       }
    }
}
于 2018-10-17T10:44:24.860 回答