使用Blob SDK下载Blob时内存占用远超实际Blob大小的问题咨询
内存占用过高的原因
MemoryStream默认的双倍扩容逻辑会产生大量冗余内存。你初始化MemoryStream时没指定初始容量,写入数据时容量不足就会申请一块大小为原容量2倍的新字节数组,旧数组要等GC扫描到才会回收。写入350MB数据的过程中,扩容到512MB容量的阶段,旧的256MB数组还没释放,光这部分就会占掉近800MB内存。而且这些超过85KB的字节数组都会分配到大对象堆,GC回收优先级低,还不会主动做内存整理,很容易让进程内存居高不下。- 你的代码会在内存里同时存三份完整的Blob数据:SDK下载时的内部缓冲区副本、
MemoryStream里存的完整二进制内容、ReadToEndAsync()读出来的完整字符串。注意.NET里字符串是UTF-16编码,每个字符占2字节,如果你的Blob是UTF-8编码的文本,350MB的二进制内容转成字符串直接就要占700MB左右的内存,相当于直接翻了倍。 - 这几部分加起来,再算上
StreamReader内部缓冲区、字符串生成时StringBuilder扩容产生的临时内存,峰值到2GB完全符合逻辑。
优化方案
按你的实际业务场景选就行:
- 如果你不需要拿到完整的全量内容(比如要把文件存本地、逐行解析日志、边下边处理数据),绝对不要把整个Blob加载到内存:
- 存本地直接用
await blobclient.DownloadToAsync("本地文件完整路径"),SDK直接把数据流写进文件,内存里只会留很小的传输缓冲区。 - 需要流处理的话直接调
await blobclient.OpenReadAsync()拿到Blob的原始读取流,边读边处理,比如逐行读文本就循环调ReadLineAsync(),处理完的内容及时释放,不要把所有数据都攒在内存里。
- 存本地直接用
- 如果你确实需要拿到完整的文件内容(比如全量文本、全量字节数组),提前消掉冗余分配:
- 先调
var prop = (await blobclient.GetPropertiesAsync()).Value;拿到Blob的实际长度ContentLength,初始化MemoryStream的时候直接指定容量:new MemoryStream((int)prop.ContentLength),这样MemoryStream从一开始就申请足够大的数组,完全不会触发双倍扩容。 - 不需要自己套一层MemoryStream+StreamReader读文本,直接调
var content = await blobclient.DownloadContentAsync();拿到BinaryData对象,调content.Value.Content.ToString()就能拿到文本,少一层中间流的内存副本。
- 先调
- 大文件下载可以调下SDK的传输配置,减少临时缓冲区占用:初始化BlobClient的时候传
BlobClientOptions,把TransferOptions.MaximumConcurrency设为2-4(不要开太高并发,并发越高临时缓冲区占的内存越多),InitialTransferSize设为1-8MB,避免SDK一次性申请过大的下载块。
内容的提问来源于stack exchange,提问作者clonebaby59
相关产品推荐
相关产品推荐

