.NET中优化GZip解压代码 降低内存占用避免OOM错误
优化GZip解压方法的内存占用方案
原代码的内存问题分析
- 输入的
data数组在整个方法执行期间被MemoryStream持有引用,无法被GC及时回收,并行执行时会堆积大量内存 - 解压后的数据先存入
MemoryStream,再通过ToArray()生成新的byte数组,此时内存中同时存在MemoryStream内部缓冲区和decompressedData数组两份解压数据 - 最终调用
Encoding.UTF8.GetString()又会生成string,内存中同时存在byte数组和string,进一步占用内存
优化方案
方案1:直接用StreamReader读取解压流(最简洁高效)
去掉中间的MemoryStream和byte数组,直接通过StreamReader读取GZipStream生成字符串,同时尽早释放输入数组的引用:
private string DecompressData(byte[] data) { using (var ms = new MemoryStream(data)) { // 立即解除对输入数组的引用,让GC可以回收该内存 data = null; using (var gzip = new GZipStream(ms, CompressionMode.Decompress)) using (var reader = new StreamReader(gzip, Encoding.UTF8)) { return reader.ReadToEnd(); } } }
优化点:
- 消除了中间的
MemoryStream和decompressedData数组,减少一次大内存分配 - 输入数组的引用被提前释放,并行执行时能更快回收内存
StreamReader以块方式读取解压数据,避免一次性加载所有内容到内存(最终string仍需完整存储,但无额外中间开销)
方案2:使用内存池进一步降低分配(极端内存紧张场景)
对于内存极度敏感的场景,使用ArrayPool<T>复用字符/字节缓冲区,减少频繁内存分配带来的碎片:
private string DecompressData(byte[] data) { using (var ms = new MemoryStream(data)) { data = null; using (var gzip = new GZipStream(ms, CompressionMode.Decompress)) using (var reader = new StreamReader(gzip, Encoding.UTF8)) { var sb = new StringBuilder(); // 从字符池租用缓冲区,避免重复分配 char[] charBuffer = ArrayPool<char>.Shared.Rent(4096); try { int charsRead; while ((charsRead = reader.Read(charBuffer, 0, charBuffer.Length)) > 0) { sb.Append(charBuffer, 0, charsRead); } return sb.ToString(); } finally { // 归还缓冲区到池,供后续复用 ArrayPool<char>.Shared.Return(charBuffer); } } } }
优化点:
- 复用字符缓冲区,减少小内存块的频繁分配与回收,降低内存碎片
StringBuilder逐步构建最终字符串,避免一次性大内存分配
并行化补充建议
- 控制并行度:不要无限制开启并行任务,可通过
ParallelOptions.MaxDegreeOfParallelism设置合理的并行数量(比如等于CPU核心数),避免内存占用突增 - 避免共享状态:确保每个并行任务的资源独立,不要共享流或缓冲区,防止线程安全问题和内存竞争
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

