如何在.NET中下载大文件(通过HTTP)?

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

这是我正在使用的代码:

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

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

我怎么能这样做?

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

WebClient类是简化方案的类。 一旦你通过简单的场景(并且你已经过),你将不得不退后一点并使用WebRequest。

使用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”答案之前写了上述内容。 我也是在早上写得太早了,所以可能需要一些盐(和测试)。

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

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


更新:更容易的选择。 让WebClient为您完成工作:使用DownloadFile方法将数据直接放入文件中。

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

我会用这样的东西

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

Akka流可以帮助使用multithreading从System.IO.Stream以小块的forms下载文件。 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> FileSink(string filename) { return Flow.Create() .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); } } }