本文主要是告訴大家一個省記憶體的方法,將整個文件夾的內容作為一個壓縮包輸出,但是實際上沒有申請那麼多的記憶體,也不需要升級創建一個壓縮包文件。原理是通過逐個讀文件然後按照壓縮包格式輸出 ...
本文主要是告訴大家一個省記憶體的方法,將整個文件夾的內容作為一個壓縮包輸出,但是實際上沒有申請那麼多的記憶體,也不需要升級創建一個壓縮包文件。原理是通過逐個讀文件然後按照壓縮包格式輸出
在每個請求的方法可以拿到 HttpContext 屬性,通過這個屬性拿到 Response 屬性,在這裡可以使用 BodyWriter 屬性,在這個屬性裡面寫入的內容將會被客戶端下載
而這個屬性可以作為 Stream 請看下麵代碼
using var stream = HttpContext.Response.BodyWriter.AsStream();
在 .NET 中可以通過 ZipArchive 將一個文件夾的文件按照壓縮文件格式寫入,還可以設置壓縮的壓縮率等,可以設置文件所在文件夾的路徑
通過在這個 stream 創建一個 ZipArchive 類,然後在這個類裡面創建文件的方法就可以做到不斷向客戶端發送文件,發送的文件都在一個壓縮包裡面
/// <summary>
/// 將一個文件夾的內容讀取為 Stream 的壓縮包
/// </summary>
/// <param name="directory"></param>
/// <param name="stream"></param>
public static async Task ReadDirectoryToZipStreamAsync(DirectoryInfo directory, Stream stream)
{
var fileList = directory.GetFiles();
using var zipArchive = new ZipArchive(stream, ZipArchiveMode.Create);
foreach (var file in fileList)
{
var relativePath = file.FullName.Replace(directory.FullName, "");
if (relativePath.StartsWith("\\") || relativePath.StartsWith("//"))
{
relativePath = relativePath.Substring(1);
}
var zipArchiveEntry = zipArchive.CreateEntry(relativePath, CompressionLevel.NoCompression);
using (var entryStream = zipArchiveEntry.Open())
{
using var toZipStream = file.OpenRead();
await toZipStream.CopyToAsync(stream);
}
await stream.FlushAsync();
}
}
上面的代碼可以讓運行的程式不需要申請和需要傳輸一樣大的記憶體空間,或者不需要先執行壓縮放在本地文件,可以不斷讀取本地文件然後上傳。讀取本地文件等都通過 CopyToAsync 自動設置緩存大小。如果不放心 CopyToAsync 方法設置的緩存大小,可以通過重載的方法手動設置緩存的大小
await toZipStream.CopyToAsync(stream, bufferSize: 100);
上面的代碼設置了文件不要壓縮,因為作為文件傳輸的時候,實際上我的業務是在內網傳輸,我的磁碟讀取速度大概是 20M 一秒,而網路傳輸是 10M 一秒,也就是此時的壓縮其實沒什麼意義,壓縮減少的內容減少的傳輸時間就和壓縮的時間差不多
如果小伙伴需要傳輸的時候壓縮,請設置 zipArchive.CreateEntry 方法
當然此方法的缺點是,也許傳輸的時候伺服器自己讀取文件炸了,此時就會傳輸的文件不對,同時客戶端不知道伺服器傳的對不對,因為壓縮的大小沒有告訴客戶端。如果要告訴客戶端壓縮後的大小就需要先在伺服器端進行壓縮。本文的方法設置的是沒有壓縮率的壓縮,大概的大小還可以告訴用戶
此方法可以如何使用?在隨意一個 Get 方法裡面就可以通過 HttpContext 傳入 Response 屬性
在使用 BodyWriter 寫入之前需要先設置 StatusCode 的值
HttpContext.Response.StatusCode = StatusCodes.Status200OK;
using var stream = HttpContext.Response.BodyWriter.AsStream();
假設需要返回的文件夾是 f:\lindexi\test\
可以通過下麵代碼的方式將文件夾輸出為壓縮包
[HttpGet]
[Route("{id}")]
public async Task Get([FromRoute] string id)
{
var folder = @"f:\lindexi\test\";
HttpContext.Response.StatusCode = StatusCodes.Status200OK;
using var stream = HttpContext.Response.BodyWriter.AsStream();
await ReadDirectoryToZipStreamAsync(new DirectoryInfo(folder), stream);
}
本地我寫了一個 PowerShell 腳本運行
For ($i=0; $i -le 100000; $i++)
{
(new-object System.Net.WebClient).DownloadFile("http://localhost:5000/File/doubi", "F:\lindexi\zip\2.zip")
}
本地運行這個腳本可以看到記憶體其實沒有 GC 也沒有溢出,我運行看到記憶體大概在 100M 左右
獲取的時候會占用一些 CPU 資源,但是很省記憶體
如果小伙伴有更好的方法歡迎告訴我