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

时间:2009-07-03 09:19:40

标签: c# .net http large-files

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

这是我正在使用的代码:

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

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

我怎么能这样做?

6 个答案:

答案 0 :(得分:37)

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

答案 1 :(得分:31)

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

答案 2 :(得分:9)

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

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


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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

我会使用像this

这样的东西

答案 5 :(得分: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);
       }
    }
}