将文件加载为字节数组,而不在内存中分配它 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#的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中转换为字节时截断字符串?

h5py 可以从内存中的字节数组加载文件吗?

在一行 C# 中为公共字节数组赋值

JAVA里String数组在内存分配中分配的空间每个占几个字节?

在 C# 中强制转换数组而不制作副本

为啥 MSVC 不在生成的汇编代码中分配 32 字节的影子空间?