将文件加载为字节数组,而不在内存中分配它 C#
Posted
技术标签:
【中文标题】将文件加载为字节数组,而不在内存中分配它 C#【英文标题】:Load file as byte array with no allocate it in memory C# 【发布时间】:2020-04-08 09:23:20 【问题描述】:我正在处理一个端点以上传文件并将其发送到 WCF 服务。
我有一个接受表单数据中的一个文件的端点。一个用户最多可以上传 50 个文件,但文件是一个一个上传(一次一个)。每个文件最大可达 5MB。因此,在代码中,我将文件读取为 Stream,并且必须将文件作为字节数组发送到 WCF 服务。 那就是问题所在。我不想在内存中分配 5MB 数组只是为了将它发送到 WCF 服务。 50 个文件 * 5MB = 250MB。很多。
未优化的代码如下所示。
public async Task<IHttpActionResult> UploadFile()
MultipartMemoryStreamProvider multipartMemoryStreamProvider = await Request.Content.ReadAsMultipartAsync();
HttpContent file = multipartMemoryStreamProvider.Contents.GetFormValue("file");
int length = (int) file.Headers.ContentLength;
byte[] fileContent = new byte[length]; // allocating up to 5MB :(
using (Stream stream = await file.ReadAsStreamAsync())
await stream.ReadAsync(fileContent, 0, length);
await _documentService.UploadAsync("filename", fileContent);
我尝试使用 ArrayPool,效果很好,但是 ArrayPool 给了我一个请求字节最少的数组。因此,我发送了更多我想要的字节(例如,对于 420KB 文件,我收到了 512KB 数组)。我可以使用 ArrayPool.Create()。但是,我不想最终得到大量的池。
此外,我检查了 Array.Resize (https://docs.microsoft.com/en-us/dotnet/api/system.array.resize?view=netframework-4.8),但它会将元素从旧数组复制到新数组(再次分配同一个数组)。
Span 没用,因为 .ToArray() 方法会分配一个新的字节数组。
你知道如何在不分配的情况下创建字节数组吗?
【问题讨论】:
如果您使用 WCF,首先您需要配置端点绑定以使用流,否则它将已经在内存中缓冲。你应该改变你的合同使用Stream
而不是byte[]
我不对这项服务负责,所以说不能将其更改为 Stream :) 我知道,这将是最好和最简单的解决方案。
如果 5 MB 是个问题,您可能应该升级您的硬件。您可以将文件保存到磁盘,然后一次只读取 1024kb 的块并发送它们。您的 ReadAsync 函数还允许设置偏移量和长度,以便您可以读取块并发送它们。
如果服务强迫你使用byte[]
,你就没有机会避免它。只需通过File.ReadAllBytes
读取所有字节
@Charles 每个文件 5MB,每个用户 50 个文件。它是 250MB。假设您有 100 个用户同时发送文件。 LOH 中分配的字节数约为 2.5GB。如果 GC 收集 LOH,它会收集所有代。这不是廉价的操作。我的 LOH 会碎片化,很多对象可能会从 Gen0 提升到 Gen1,从 Gen1 提升到 Gen2,只是因为我以愚蠢的方式分配数组。
【参考方案1】:
没有办法“创建字节数组而不分配”。只要 API 需要数组,您就必须创建一个新数组,因为大小是实例的一部分(您可以做的唯一优化是重用相同大小的数组)。
所以你的选择是:
忍受它。如果内存分配有问题,您可以强制进行垃圾回收。 将 API 更改为流式传输或支持部分上传或使用常规 POST 而不是 WCF 方法。【讨论】:
以上是关于将文件加载为字节数组,而不在内存中分配它 C#的主要内容,如果未能解决你的问题,请参考以下文章