POI 3.9中SXSSFWorkbook处理大数据时autoSizeColumn列宽异常问题
这个坑我之前踩过!咱们先拆解问题的原因,再给你具体的解决方案:
核心原因
你设置了mySheet.setRandomAccessWindowSize(1000),这是SXSSF为了节省内存的关键机制:当行数超过1000时,最早的那些行会被刷到磁盘上,不再保存在内存中。而autoSizeColumn的工作原理是遍历该列下所有内存中的单元格内容,计算最大宽度后调整列宽——磁盘上的行POI根本读不到,所以超过1000行后,只有最后1000行参与了宽度计算,前面的行内容被忽略,导致列宽不正确。
另外,你的代码里还有一个明显的错误:autoSizeColumn的循环遍历的是行数,而不是列数!比如如果表格有1000行、10列,你会循环1000次去调整第0到999列的宽度,但实际只有10列,这不仅无效,还可能引发索引越界。
解决方案
针对这两个问题,给你两种可行的解决思路:
方案1:调整随机窗口大小(适合数据量不大的场景)
如果你的服务器内存足够,直接把setRandomAccessWindowSize的值设为比实际行数大的数,让所有行都保存在内存中,这样autoSizeColumn就能正常工作:
SXSSFSheet mySheet = (SXSSFSheet) workbook.createSheet("New"); // 设为比预期最大行数大的值,比如20000 mySheet.setRandomAccessWindowSize(20000);
同时一定要修正autoSizeColumn的循环逻辑,改成遍历列数:
if(mySheet.getPhysicalNumberOfRows() > 0){ // 获取第一行的列数(假设第一行有所有列) int columnCount = mySheet.getRow(0).getLastCellNum(); for(int i = 0; i < columnCount; i++) { mySheet.autoSizeColumn(i); // 可以给列宽加一点缓冲,避免内容被截断 mySheet.setColumnWidth(i, mySheet.getColumnWidth(i) + 200); } }
方案2:手动计算列宽(适合大数据量场景,推荐)
如果数据量特别大,调大窗口会导致内存溢出,那最好在写入每行数据时,手动记录每列的最大内容宽度,最后用setColumnWidth设置:
// 初始化数组保存每列的最大宽度(单位:1/256个字符宽度,POI的标准单位) int[] maxColumnWidths = new int[data.length]; // 先设一个默认宽度,比如10个字符 Arrays.fill(maxColumnWidths, 10 * 256); for(int rc = 0; rc < rcdata.length; rc++){ Row row = mySheet.createRow(rc); for(int hr = 0; hr < data.length; hr++){ Cell cell = row.createCell(hr); String cellValue = list.get(he); // 这里注意你的数据索引是否正确,比如可能需要结合rc和hr cell.setCellValue(cellValue); // 计算当前单元格内容的宽度(考虑字体的话可以更精确,这里简化处理) int contentWidth = cellValue.length() * 256; // 加上单元格左右边距的缓冲,乘以1.2倍 contentWidth = (int) (contentWidth * 1.2); // 更新该列的最大宽度 if(contentWidth > maxColumnWidths[hr]){ maxColumnWidths[hr] = contentWidth; } } } // 最后设置每列的宽度 for(int i = 0; i < maxColumnWidths.length; i++) { mySheet.setColumnWidth(i, maxColumnWidths[i]); }
这种方法不依赖内存中的行数据,不管多少行都能准确计算出合适的列宽,完全避开SXSSF的内存限制问题。
总结
优先修正autoSizeColumn的循环逻辑,然后根据数据量选择调整窗口大小或者手动计算列宽——后者更适合大数据量的场景,稳定性更高。
内容的提问来源于stack exchange,提问作者user2553589

