第16章-大文件处理与性能优化
第十六章:大文件处理与性能优化
16.1 大文件处理概述
16.1.1 内存挑战
处理大型Excel文件时,常规方法可能导致内存问题:
- XSSFWorkbook:将整个文件加载到内存,百万行数据可能占用数GB内存
- HSSFWorkbook:有65536行的限制
- 内存溢出(OutOfMemoryException)风险
16.1.2 解决方案
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| SXSSFWorkbook | 大量数据写入 | 低内存占用 | 只能写入,不能读取 |
| SAX事件解析 | 大量数据读取 | 流式读取,内存低 | 实现复杂 |
| 分批处理 | 通用场景 | 简单易用 | 需要多次IO |
16.2 SXSSFWorkbook流式写入
16.2.1 基本使用
using NPOI.XSSF.Streaming;
using NPOI.XSSF.UserModel;
using NPOI.SS.UserModel;
/// <summary>
/// 使用SXSSFWorkbook写入大量数据
/// </summary>
public static void WriteLargeExcel(string filePath, int rowCount)
{
// 创建SXSSF工作簿(默认内存保留100行)
SXSSFWorkbook workbook = new SXSSFWorkbook();
// 或指定内存保留行数
// SXSSFWorkbook workbook = new SXSSFWorkbook(null, 500); // 保留500行
ISheet sheet = workbook.CreateSheet("大数据");
// 创建表头
IRow headerRow = sheet.CreateRow(0);
headerRow.CreateCell(0).SetCellValue("序号");
headerRow.CreateCell(1).SetCellValue("名称");
headerRow.CreateCell(2).SetCellValue("数值");
headerRow.CreateCell(3).SetCellValue("日期");
// 写入大量数据
Random rand = new Random();
for (int i = 1; i <= rowCount; i++)
{
IRow row = sheet.CreateRow(i);
row.CreateCell(0).SetCellValue(i);
row.CreateCell(1).SetCellValue($"Item-{i}");
row.CreateCell(2).SetCellValue(rand.NextDouble() * 10000);
row.CreateCell(3).SetCellValue(DateTime.Now.AddDays(-rand.Next(365)));
// 进度显示
if (i % 100000 == 0)
{
Console.WriteLine($"已写入 {i:N0} 行...");
}
}
// 保存文件
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
workbook.Write(fs);
}
// 清理临时文件
workbook.Dispose();
Console.WriteLine($"完成!共写入 {rowCount:N0} 行数据");
}
16.2.2 自定义内存设置
/// <summary>
/// 创建自定义配置的SXSSF工作簿
/// </summary>
public static SXSSFWorkbook CreateOptimizedWorkbook(int rowsInMemory = 100)
{
// 基于XSSFWorkbook创建
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
// 创建SXSSF工作簿,指定内存保留行数
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook, rowsInMemory);
// 启用压缩临时文件(减少磁盘占用)
sxssfWorkbook.CompressTempFiles = true;
return sxssfWorkbook;
}
16.2.3 SXSSFWorkbook注意事项
// 1. SXSSF限制:
// - 不能读取已写入的行(已刷新到磁盘)
// - 不支持某些功能(如自动调整列宽对已刷新行)
// - 必须调用Dispose()清理临时文件
// 2. 自动调整列宽需要在刷新前调用
sheet.AutoSizeColumn(0); // 仅对内存中的行有效
// 3. 显式刷新行到磁盘
((SXSSFSheet)sheet).FlushRows(100); // 刷新保留最近100行
// 4. 使用完毕后清理
workbook.Dispose();
16.3 大文件读取优化
16.3.1 SAX事件解析
using NPOI.XSSF.EventModel;
using NPOI.OpenXml4Net.OPC;
using System.Xml;
/// <summary>
/// 使用SAX方式读取大型Excel文件
/// </summary>
public class LargeExcelReader
{
/// <summary>
/// SAX方式读取Excel
/// </summary>
public static void ReadLargeExcel(string filePath, Action<int, List<string>> rowHandler)
{
using (var package = OPCPackage.Open(filePath, PackageAccess.READ))
{
XSSFReader reader = new XSSFReader(package);
SharedStringsTable sst = reader.GetSharedStringsTable();
// 获取工作表
var sheets = reader.GetSheetsData();
while (sheets.MoveNext())
{
Stream sheetStream = sheets.Current;
ReadSheet(sheetStream, sst, rowHandler);
}
}
}
private static void ReadSheet(Stream sheetStream, SharedStringsTable sst,
Action<int, List<string>> rowHandler)
{
using (XmlReader reader = XmlReader.Create(sheetStream))
{
int rowIndex = 0;
List<string> rowData = new List<string>();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name == "row")
{
rowIndex = int.Parse(reader.GetAttribute("r") ?? "0");
rowData.Clear();
}
else if (reader.Name == "c")
{
string cellType = reader.GetAttribute("t");
string cellValue = "";
if (reader.ReadToDescendant("v"))
{
string value = reader.ReadElementContentAsString();
if (cellType == "s") // 共享字符串
{
int idx = int.Parse(value);
cellValue = sst.GetItemAt(idx).ToString();
}
else
{
cellValue = value;
}
}
rowData.Add(cellValue);
}
}
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "row")
{
rowHandler(rowIndex, rowData);
}
}
}
}
}
// 使用示例
// LargeExcelReader.ReadLargeExcel("large.xlsx", (rowNum, cells) => {
// Console.WriteLine($"行 {rowNum}: {string.Join(", ", cells)}");
// });
16.3.2 分批读取
/// <summary>
/// 分批读取Excel数据
/// </summary>
public static IEnumerable<List<object[]>> ReadExcelInBatches(string filePath, int batchSize = 1000)
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
IWorkbook workbook = new XSSFWorkbook(fs);
ISheet sheet = workbook.GetSheetAt(0);
int totalRows = sheet.LastRowNum + 1;
int currentRow = 0;
while (currentRow < totalRows)
{
List<object[]> batch = new List<object[]>();
int endRow = Math.Min(currentRow + batchSize, totalRows);
for (int i = currentRow; i < endRow; i++)
{
IRow row = sheet.GetRow(i);
if (row == null) continue;
object[] rowData = new object[row.LastCellNum];
for (int j = 0; j < row.LastCellNum; j++)
{
ICell cell = row.GetCell(j);
rowData[j] = GetCellValue(cell);
}
batch.Add(rowData);
}
currentRow = endRow;
yield return batch;
}
}
}
private static object GetCellValue(ICell cell)
{
if (cell == null) return null;
return cell.CellType switch
{
CellType.Numeric => DateUtil.IsCellDateFormatted(cell)
? cell.DateCellValue : cell.NumericCellValue,
CellType.String => cell.StringCellValue,
CellType.Boolean => cell.BooleanCellValue,
CellType.Formula => cell.CachedFormulaResultType == CellType.Numeric
? cell.NumericCellValue : cell.StringCellValue,
_ => null
};
}
16.4 性能优化技巧
16.4.1 样式复用
/// <summary>
/// 样式缓存管理器
/// </summary>
public class StyleCache
{
private readonly IWorkbook _workbook;
private readonly Dictionary<string, ICellStyle> _cache = new();
public StyleCache(IWorkbook workbook)
{
_workbook = workbook;
}
public ICellStyle GetOrCreate(string key, Action<ICellStyle> configureStyle)
{
if (!_cache.TryGetValue(key, out ICellStyle style))
{
style = _workbook.CreateCellStyle();
configureStyle(style);
_cache[key] = style;
}
return style;
}
// 预定义样式
public ICellStyle HeaderStyle => GetOrCreate("header", s => {
s.Alignment = HorizontalAlignment.Center;
IFont font = _workbook.CreateFont();
font.IsBold = true;
s.SetFont(font);
});
public ICellStyle NumberStyle => GetOrCreate("number", s => {
IDataFormat format = _workbook.CreateDataFormat();
s.DataFormat = format.GetFormat("#,##0.00");
});
public ICellStyle DateStyle => GetOrCreate("date", s => {
IDataFormat format = _workbook.CreateDataFormat();
s.DataFormat = format.GetFormat("yyyy-MM-dd");
});
}
// 使用示例
StyleCache cache = new StyleCache(workbook);
for (int i = 0; i < 100000; i++)
{
IRow row = sheet.CreateRow(i);
row.CreateCell(0).CellStyle = cache.NumberStyle; // 复用样式
row.CreateCell(1).CellStyle = cache.DateStyle;
}
16.4.2 减少对象创建
/// <summary>
/// 高效写入大量数据
/// </summary>
public static void EfficientWrite(ISheet sheet, List<object[]> data)
{
// 预先获取样式
IWorkbook workbook = sheet.Workbook;
ICellStyle defaultStyle = workbook.CreateCellStyle();
for (int i = 0; i < data.Count; i++)
{
IRow row = sheet.CreateRow(i);
object[] rowData = data[i];
for (int j = 0; j < rowData.Length; j++)
{
ICell cell = row.CreateCell(j);
// 直接设置值,避免类型判断
object value = rowData[j];
if (value is string s)
cell.SetCellValue(s);
else if (value is double d)
cell.SetCellValue(d);
else if (value is DateTime dt)
cell.SetCellValue(dt);
else if (value is bool b)
cell.SetCellValue(b);
else if (value != null)
cell.SetCellValue(value.ToString());
}
}
}
16.4.3 并行处理
/// <summary>
/// 并行处理多个工作表
/// </summary>
public static void ProcessSheetsInParallel(string filePath)
{
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
IWorkbook workbook = new XSSFWorkbook(fs);
// 并行处理各工作表
Parallel.For(0, workbook.NumberOfSheets, i =>
{
ISheet sheet = workbook.GetSheetAt(i);
ProcessSheet(sheet);
});
}
private static void ProcessSheet(ISheet sheet)
{
// 处理单个工作表的逻辑
for (int i = sheet.FirstRowNum; i <= sheet.LastRowNum; i++)
{
IRow row = sheet.GetRow(i);
// 处理行数据...
}
}
16.4.4 内存管理
/// <summary>
/// 内存管理最佳实践
/// </summary>
public static void MemoryManagementExample()
{
// 1. 使用 using 确保资源释放
using (FileStream fs = new FileStream("file.xlsx", FileMode.Open))
using (IWorkbook workbook = new XSSFWorkbook(fs))
{
// 处理文件...
}
// 2. 处理大文件时强制GC
GC.Collect();
GC.WaitForPendingFinalizers();
// 3. 使用SXSSF时必须Dispose
SXSSFWorkbook sxssf = new SXSSFWorkbook();
try
{
// 写入操作...
}
finally
{
sxssf.Dispose(); // 清理临时文件
}
// 4. 大数据量处理时分批
const int batchSize = 10000;
for (int batch = 0; batch < totalBatches; batch++)
{
ProcessBatch(batch, batchSize);
// 定期强制GC
if (batch % 10 == 0)
{
GC.Collect();
}
}
}
16.5 性能测试与基准
16.5.1 性能测试工具
/// <summary>
/// 简单性能测试
/// </summary>
public static class PerformanceTest
{
public static void MeasureWritePerformance(int rowCount)
{
Console.WriteLine($"测试写入 {rowCount:N0} 行数据...\n");
// 测试普通XSSFWorkbook
Console.WriteLine("XSSFWorkbook:");
var sw1 = Stopwatch.StartNew();
WriteWithXSSF(rowCount, "test_xssf.xlsx");
sw1.Stop();
Console.WriteLine($" 耗时: {sw1.Elapsed.TotalSeconds:F2}秒");
Console.WriteLine($" 内存: {GC.GetTotalMemory(false) / 1024 / 1024}MB\n");
GC.Collect();
GC.WaitForPendingFinalizers();
// 测试SXSSFWorkbook
Console.WriteLine("SXSSFWorkbook:");
var sw2 = Stopwatch.StartNew();
WriteWithSXSSF(rowCount, "test_sxssf.xlsx");
sw2.Stop();
Console.WriteLine($" 耗时: {sw2.Elapsed.TotalSeconds:F2}秒");
Console.WriteLine($" 内存: {GC.GetTotalMemory(false) / 1024 / 1024}MB\n");
}
private static void WriteWithXSSF(int rowCount, string filePath)
{
using var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet("Data");
for (int i = 0; i < rowCount; i++)
{
var row = sheet.CreateRow(i);
row.CreateCell(0).SetCellValue(i);
row.CreateCell(1).SetCellValue($"Row {i}");
}
using var fs = new FileStream(filePath, FileMode.Create);
workbook.Write(fs);
}
private static void WriteWithSXSSF(int rowCount, string filePath)
{
using var workbook = new SXSSFWorkbook();
var sheet = workbook.CreateSheet("Data");
for (int i = 0; i < rowCount; i++)
{
var row = sheet.CreateRow(i);
row.CreateCell(0).SetCellValue(i);
row.CreateCell(1).SetCellValue($"Row {i}");
}
using var fs = new FileStream(filePath, FileMode.Create);
workbook.Write(fs);
workbook.Dispose();
}
}
16.6 本章小结
本章详细介绍了NPOI处理大文件和性能优化的方法。通过本章学习,你应该掌握:
- SXSSFWorkbook流式写入的使用
- SAX事件解析读取大文件
- 分批处理数据的方法
- 样式复用减少内存占用
- 并行处理提高效率
- 内存管理最佳实践
- 性能测试和基准方法
大文件处理是企业应用中的常见需求,合理使用这些技术可以有效避免内存问题。
下一章预告:第十七章将介绍企业级应用最佳实践。

浙公网安备 33010602011771号