使用CsvHelper转换CSV为Excel时内存占用过高的优化咨询
问题原因定位
首先可以明确:高内存占用不是CsvHelper导致的,CsvHelper默认是流式读取CSV,不会一次性加载全量文件到内存,问题出在两个方面:
- ClosedXML本身是基于内存的Office操作库,会将整个工作簿的所有单元格、元数据全部加载到内存中维护,10万行14列的数据集加上单元格的附加属性,内存占用本身就会很高
- 你的代码中额外引入了
MemoryStream存储完整的生成后Excel文件,相当于全量Excel数据在内存中冗余存储了一份,进一步抬高了内存峰值
可行优化方案
方案1:基于现有ClosedXML依赖最小改动优化(内存预计降到500MB~800MB)
- 移除冗余的MemoryStream,直接将Workbook保存到文件流,避免全量Excel数据在内存中重复缓存
- 替换逐单元格赋值逻辑,使用ClosedXML内置的
InsertData批量插入数据,减少单元格对象的重复创建开销 - 开启ClosedXML的性能优化配置
优化后的代码示例:
using ClosedXML.Excel; using CsvHelper; using System.Globalization; using System.IO; using System.Collections.Generic; namespace ConsoleApp2.Conversion { public class CsvHelperExcelWriter { public void Process(string csvFilePath) { // 配置ClosedXML全局性能选项,减少默认样式开销 XLWorkbook.DefaultStyle.Font.FontSize = 11; XLWorkbook.DefaultStyle.Font.FontName = "Calibri"; using var workbook = new XLWorkbook(XLEventTracking.Disabled); var worksheet = workbook.AddWorksheet("Sheet1"); using var csv = new CsvReader(new StreamReader(csvFilePath), CultureInfo.InvariantCulture); csv.Read(); if (string.IsNullOrWhiteSpace(csv.Context.Parser.RawRecord)) return; // 写入表头 csv.ReadHeader(); worksheet.Cell(1, 1).InsertData(new[] { csv.HeaderRecord }, transpose: false); // 批量写入数据,每次读1000行插入一次,可根据需要调整批次大小 var batch = new List<string[]>(1000); int rowOffset = 2; while (csv.Read()) { batch.Add(csv.Parser.Record); if (batch.Count >= 1000) { worksheet.Cell(rowOffset, 1).InsertData(batch, transpose: false); rowOffset += batch.Count; batch.Clear(); } } // 插入剩余不满1000行的批次 if (batch.Count > 0) { worksheet.Cell(rowOffset, 1).InsertData(batch, transpose: false); } // 直接保存到文件,无需经过MemoryStream中转 workbook.SaveAs(@"C:\Projects\POC\SampleFile\Excel\100000 Sales Records with CsvHelper.xlsx"); } } }
方案2:替换为流式Excel写入库(内存预计降到100MB以下)
如果需要更低的内存占用,建议放弃ClosedXML,改用支持流式写入的Excel库,比如NPOI的SXSSF实现,或者EPPlus的流式写入接口,这类库会将超出缓存数量的行临时写入磁盘临时文件,内存中仅保留最近几千行数据,10万行级别的转换内存峰值通常可以控制在几十MB。
示例使用NPOI SXSSF的核心代码逻辑:
using NPOI.XSSF.Streaming; using CsvHelper; using System.Globalization; using System.IO; // 核心处理逻辑 public void Process(string csvFilePath) { // 仅保留最近1000行在内存,其余写入临时文件 var workbook = new SXSSFWorkbook(1000); var sheet = workbook.CreateSheet("Sheet1"); using var csv = new CsvReader(new StreamReader(csvFilePath), CultureInfo.InvariantCulture); csv.Read(); if (string.IsNullOrWhiteSpace(csv.Context.Parser.RawRecord)) return; // 写表头 csv.ReadHeader(); var headerRow = sheet.CreateRow(0); for (int i = 0; i < csv.HeaderRecord.Length; i++) { headerRow.CreateCell(i).SetCellValue(csv.HeaderRecord[i]); } // 逐行写入,内存仅保留最近1000行 int rowIndex = 1; while (csv.Read()) { var row = sheet.CreateRow(rowIndex); for (int i = 0; i < csv.Parser.Record.Length; i++) { row.CreateCell(i).SetCellValue(csv.GetField(i)); } rowIndex++; } // 保存到文件 using var fs = new FileStream(@"C:\Projects\POC\SampleFile\Excel\100000 Sales Records with CsvHelper.xlsx", FileMode.Create, FileAccess.Write); workbook.Write(fs); // 清理生成的临时文件 workbook.Dispose(); }
内容的提问来源于stack exchange,提问作者Dulmi
相关产品推荐
相关产品推荐

