Apache POI SXSSFWorkbook自动调整列宽耗时过长,求优化方法
优化Apache POI SXSSF自动列宽的耗时方案
核心问题分析
trackAllColumnsForAutoSizing()会强制SXSSF缓存所有行的单元格内容(哪怕已刷入磁盘),而autoSizeColumn()需要遍历全部10万行计算每列最大宽度,二者叠加导致IO和计算量暴增,是耗时翻倍的核心原因。
优化方案
1. 手动计算列宽(最优方案,耗时最低)
完全绕过POI的自动计算逻辑,在生成数据时同步记录每列的最大字符宽度,最后手动设置列宽。
实现思路:
- 初始化数组记录每列的最大宽度(考虑中文等宽字符占比,中文按2个英文字符宽度计算)
- 写入每个单元格时,计算内容的等效字符长度,更新对应列的最大值
- 用
sheet.setColumnWidth()设置列宽(POI列宽单位为1/256个字符宽度)
代码示例:
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.xssf.streaming.SXSSFSheet; import java.util.Arrays; // 假设你有5列数据 int columnCount = 5; int[] maxColumnWidths = new int[columnCount]; // 设置默认最小宽度(对应10个英文字符) Arrays.fill(maxColumnWidths, 10); SXSSFSheet sheet = workbook.createSheet("数据"); for (int rowNum = 0; rowNum < 100000; rowNum++) { Row row = sheet.createRow(rowNum); for (int colNum = 0; colNum < columnCount; colNum++) { String cellValue = generateCellValue(rowNum, colNum); // 替换成你的数据生成逻辑 Cell cell = row.createCell(colNum); cell.setCellValue(cellValue); // 计算等效字符长度:中文/全角字符算2,英文/半角算1 int effectiveLength = 0; for (char c : cellValue.toCharArray()) { effectiveLength += (Character.toString(c).matches("[\\u4E00-\\u9FA5\\uFF00-\\uFFEF]")) ? 2 : 1; } // 更新列的最大宽度 if (effectiveLength > maxColumnWidths[colNum]) { maxColumnWidths[colNum] = effectiveLength; } } } // 最终设置列宽,加上2个字符的边距,限制最大宽度为60个字符 for (int colNum = 0; colNum < columnCount; colNum++) { int width = (maxColumnWidths[colNum] + 2) * 256; if (width > 60 * 256) { width = 60 * 256; } sheet.setColumnWidth(colNum, width); }
2. 抽样计算自动列宽(折中方案,实现简单)
如果不想手动处理字符宽度,可只抽取部分行计算列宽,避免遍历全部10万行。比如取前1000行、中间随机500行、最后1000行,覆盖数据的大概率分布。
代码示例:
SXSSFSheet sheet = workbook.createSheet("数据"); // 先生成所有数据(和原有逻辑一致) generateAllData(sheet); int columnCount = 5; int[] maxColumnWidths = new int[columnCount]; Arrays.fill(maxColumnWidths, 10); int sampleSize = 1000; // 抽样前1000行 for (int rowNum = 0; rowNum < Math.min(sampleSize, sheet.getLastRowNum()); rowNum++) { Row row = sheet.getRow(rowNum); if (row == null) continue; for (int colNum = 0; colNum < columnCount; colNum++) { Cell cell = row.getCell(colNum); if (cell == null || cell.getCellType() != Cell.CELL_TYPE_STRING) continue; String value = cell.getStringCellValue(); int effectiveLength = 0; for (char c : value.toCharArray()) { effectiveLength += (Character.toString(c).matches("[\\u4E00-\\u9FA5\\uFF00-\\uFFEF]")) ? 2 : 1; } if (effectiveLength > maxColumnWidths[colNum]) { maxColumnWidths[colNum] = effectiveLength; } } } // 抽样后1000行 int lastRow = sheet.getLastRowNum(); for (int rowNum = Math.max(0, lastRow - sampleSize); rowNum <= lastRow; rowNum++) { Row row = sheet.getRow(rowNum); if (row == null) continue; for (int colNum = 0; colNum < columnCount; colNum++) { Cell cell = row.getCell(colNum); if (cell == null || cell.getCellType() != Cell.CELL_TYPE_STRING) continue; String value = cell.getStringCellValue(); int effectiveLength = 0; for (char c : value.toCharArray()) { effectiveLength += (Character.toString(c).matches("[\\u4E00-\\u9FA5\\uFF00-\\uFFEF]")) ? 2 : 1; } if (effectiveLength > maxColumnWidths[colNum]) { maxColumnWidths[colNum] = effectiveLength; } } } // 设置列宽 for (int colNum = 0; colNum < columnCount; colNum++) { int width = (maxColumnWidths[colNum] + 2) * 256; if (width > 60 * 256) width = 60 * 256; sheet.setColumnWidth(colNum, width); }
3. 关闭不必要的跟踪与优化SXSSF参数
- 必须删除
sheet.trackAllColumnsForAutoSizing():该方法会强制SXSSF缓存所有单元格内容,大幅增加IO和内存开销。 - 调整SXSSF内存行数:当前设置的50000行内存占用过高,建议调整为10000或更低(比如5000),减少内存压力,提升磁盘刷写效率:
SXSSFWorkbook workbook = new SXSSFWorkbook(new XSSFWorkbook(), 10000, false, true);
方案对比
| 方案 | 耗时预估 | 实现复杂度 | 精度 |
|---|---|---|---|
| 手动计算列宽 | 和原生成时间接近(~15秒) | 中等 | 完全可控 |
| 抽样计算列宽 | ~30-60秒 | 低 | 满足大部分场景 |
| 原POI自动计算 | ~3分钟 | 低 | 最高 |
内容的提问来源于stack exchange,提问作者Dipak Dutta
相关产品推荐
相关产品推荐

