Apache POI SXSSF导出大Excel速度慢 23万行CSV转Excel30秒超时如何优化
问题背景
使用Apache POI的SXSSFWorkbook实现CSV文件转Excel功能,当前处理包含23万条记录、50列的CSV文件时,读写全流程总耗时约1分钟,需要将总耗时压缩到30秒以内以满足网关30秒的连接超时阈值要求。已测试Fastexcel工具,其处理耗时和POI流式处理无明显差异,无优化效果。
现有核心实现代码
主逻辑代码
public static byte[] readCsvAndWriteExcel(String filePath, File csvFile) { logger.info("Read csv and write excel has been started for {}", csvFile.getName()); String line; BufferedReader bufferedReader = readFile(filePath); SXSSFWorkbook workbook = new SXSSFWorkbook(SXSSFWorkbook.DEFAULT_WINDOW_SIZE); workbook.setCompressTempFiles(true); int rowNumber = 0; int cellNumber; SXSSFRow row; SXSSFSheet sheet = workbook.createSheet(FilenameUtils.getBaseName(csvFile.getName())); try { while ((line = bufferedReader.readLine()) != null) { cellNumber = 0; row = sheet.createRow(rowNumber++); addCell(cellNumber, row, line.trim().split(SEPERATOR_PATTERN)); } } catch (IOException e) { logger.error("File is not found: {}", filePath, e); throw new Exception("Exception"); } logger.info("Read csv and write excel has been finished for {}", csvFile.getName()); return retrieveBytesOfXlsxFile(workbook); }
单元格写入代码
private static void addCell(int cellNumber, SXSSFRow row, String[] splitRow) { for (String field : splitRow) { SXSSFCell cell = row.createCell(cellNumber++); cell.setCellValue(field); } }
现有实现存在的问题
- 未指定CSV读取缓冲区大小:默认BufferedReader缓冲区仅8KB,处理大文件时频繁IO读取会拖慢性能
- 字符串分割逻辑效率低:使用正则表达式作为分隔符(SEPERATOR_PATTERN)做字符串split,单线程循环23万次的正则匹配开销非常大
- line.trim()冗余操作:如果CSV行首尾无多余空格,trim属于无效计算;即使有也可以和分割逻辑合并处理
- SXSSF窗口大小配置不合理:默认窗口大小为100,内存中仅保留100行数据,频繁刷写临时文件会带来额外IO开销
- 全量字节数组返回逻辑开销大:retrieveBytesOfXlsxFile方法需要把整个生成的Excel文件读到内存再返回,大文件场景下内存拷贝和IO开销很高
- addCell方法无类型处理:所有字段都按字符串写入单元格,POI处理字符串单元格的开销远高于数值、日期等特定类型,还会额外生成共享字符串表
可调整优化方向
- CSV读取优化:使用
BufferedReader(new FileReader(filePath), 1024*1024)指定1MB以上的缓冲区,减少IO读取次数;也可替换为OpenCSV等专业CSV解析库的流式解析能力,避免手动处理行分割的边界问题 - 分割逻辑优化:将正则分隔符替换为普通字符分割,比如逗号分隔直接用
String.split(","),如果是多字符分隔优先用StringTokenizer或者手动实现分割逻辑,避免正则匹配开销 - SXSSF配置优化:调大SXSSF窗口大小到1000~5000(根据服务器内存调整),例如
new SXSSFWorkbook(2000),减少临时文件刷写次数;如果内存充足可以关闭临时文件压缩workbook.setCompressTempFiles(false),减少CPU压缩开销 - 单元格写入优化:禁用POI的共享字符串表,创建单元格时指定类型为字符串直接写入:
cell.setCellValue(new XSSFRichTextString(field)),避免共享字符串表的频繁写入查询开销;如果能识别字段类型,将数值、日期等字段转换为对应类型写入,进一步降低开销 - 返回逻辑优化:不要把整个Excel读取为字节数组返回,改为直接往ServletResponse的输出流写入,避免大字节数组的内存拷贝和堆内存占用,通常可节省5~10秒的最后序列化时间
- 并行处理优化:如果服务器CPU核心充足,可以将CSV读取和Excel写入做异步解耦,用队列缓存已解析的行数据,生产者线程读CSV解析行,消费者线程写Excel,充分利用多核CPU资源
内容的提问来源于stack exchange,提问作者Berkay Bingol
相关产品推荐
相关产品推荐

