在C#中部分下载和序列化大文件?

作为我大学即将开展的项目的一部分,我需要编写一个客户端,从服务器下载媒体文件并将其写入本地磁盘。 由于这些文件可能非常大,我需要实现部分下载和序列化,以避免过多的内存使用。

我想出了什么:

namespace PartialDownloadTester { using System; using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Text; public class DownloadClient { public static void Main(string[] args) { var dlc = new DownloadClient(args[0], args[1], args[2]); dlc.DownloadAndSaveToDisk(); Console.ReadLine(); } private WebRequest request; // directory of file private string dir; // full file identifier private string filePath; public DownloadClient(string uri, string fileName, string fileType) { this.request = WebRequest.Create(uri); this.request.Method = "GET"; var sb = new StringBuilder(); sb.Append("C:\\testdata\\DownloadedData\\"); this.dir = sb.ToString(); sb.Append(fileName + "." + fileType); this.filePath = sb.ToString(); } public void DownloadAndSaveToDisk() { // make sure directory exists this.CreateDir(); var response = (HttpWebResponse)request.GetResponse(); Console.WriteLine("Content length: " + response.ContentLength); var rStream = response.GetResponseStream(); int bytesRead = -1; do { var buf = new byte[2048]; bytesRead = rStream.Read(buf, 0, buf.Length); rStream.Flush(); this.SerializeFileChunk(buf); } while (bytesRead != 0); } private void CreateDir() { if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } } private void SerializeFileChunk(byte[] bytes) { Contract.Requires(!Object.ReferenceEquals(bytes, null)); FileStream fs = File.Open(filePath, FileMode.Append); fs.Write(bytes, 0, bytes.Length); fs.Flush(); fs.Close(); } } } 

出于测试目的,我使用了以下参数:

“http://sofzh.miximages.com/c%23/mufc_abc.jpg”“mufc_abc”“jpg”

然而,即使内容长度打印63780(即图像的实际尺寸),图片也是不完整的(只有第一个~10%看起来正确)。

所以我的问题是:

  1. 这是部分下载和序列化的正确方法还是有更好/更简单的方法?
  2. 响应流的完整内容是否存储在客户端内存中? 如果是这种情况,我是否需要使用HttpWebRequest.AddRange从服务器部分下载数据以节省我的客户端内存?
  3. 序列化怎么会失败,我得到一个破碎的图像?
  4. 当我使用FileMode.Append时,是否会引入大量开销? (msdn声明此选项“寻求到文件的末尾”)

提前致谢

我不知道这是否是问题的根源,但是我会改变这样的循环

 const int ChunkSize = 2048; var buf = new byte[ChunkSize]; var rStream = response.GetResponseStream(); do { int bytesRead = rStream.Read(buf, 0, ChunkSize); if (bytesRead > 0) { this.SerializeFileChunk(buf, bytesRead); } } while (bytesRead == ChunkSize); 

serialize方法将获得一个额外的参数

 private void SerializeFileChunk(byte[] bytes, int numBytes) 

然后写入正确的字节数

 fs.Write(bytes, 0, numBytes); 

更新:

我没有看到每次关闭和重新打开文件的必要性。 我也会使用using语句来关闭资源,即使应该发生exception也是如此。 using语句最后调用资源的Dispose()方法,在文件流的情况下调用Close()using可以应用于实现IDisposable所有类型。

 var buf = new byte[2048]; using (var rStream = response.GetResponseStream()) { using (FileStream fs = File.Open(filePath, FileMode.Append)) { do { bytesRead = rStream.Read(buf, 0, buf.Length); fs.Write(bytes, 0, bytesRead); } while (...); } } 

using语句就是这样的

 { var rStream = response.GetResponseStream(); try { // do some work with rStream here. } finally { if (rStream != null) { rStream.Dispose(); } } } 

您绝对可以使用WebClient简化代码:

 class Program { static void Main() { DownloadClient("http://sofzh.miximages.com/c%23/mufc_abc.jpg", "mufc_abc.jpg"); } public static void DownloadClient(string uri, string fileName) { using (var client = new WebClient()) { using (var stream = client.OpenRead(uri)) { // work with chunks of 2KB => adjust if necessary const int chunkSize = 2048; var buffer = new byte[chunkSize]; using (var output = File.OpenWrite(fileName)) { int bytesRead; while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { output.Write(buffer, 0, bytesRead); } } } } } } 

注意我是如何只写入我实际从套接字读取到输出文件而不是整个2KB缓冲区的字节数。

以下是Microsoft的解决方案: http : //support.microsoft.com/kb/812406