Apache POI中XSSF与SXSSFWorkbook混合使用报错求助
解决Apache POI中XSSF转SXSSF后修改已刷盘行的错误问题
我明白你遇到的困境了——同一个工作表里有两个表格,需要分别用XSSF处理行移动(Locations表)和SXSSF流式写入(Devices表),但转成SXSSF后修改行时触发了IllegalArgumentException。这个错误的核心原因很明确:SXSSFWorkbook会把超过内存缓存窗口的行刷到磁盘,已刷盘的行无法再被修改。默认情况下,Apache POI 3.15的SXSSF缓存窗口大小是100行,当你把XSSFWorkbook转成SXSSF时,原有的行如果超过这个数量,前面的行就会被刷盘,后续再操作这些行就会报错。
下面给你两种针对性的解决方案,你可以根据数据量大小选择:
方案1:直接用SXSSF处理所有操作(适合Locations表数据量小的场景)
其实SXSSFSheet也支持shiftRows和copyRows操作,只要这些行还在内存缓存里。你可以直接创建SXSSFWorkbook并设置足够大的缓存窗口,确保Locations表的所有行都不会被刷盘,这样就能一次性完成行移动、样式复制和流式写入:
FileInputStream inputStream = new FileInputStream(new File(filePath)); // 创建SXSSFWorkbook时设置足够大的缓存窗口(比如1000,根据你的Locations表行数调整) SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(inputStream), 1000); SXSSFSheet sxssfSheet = sxssfWorkbook.getSheet("Sheet1"); // 1. 处理Locations表的行移动 sxssfSheet.shiftRows(tab1HeaderRowNum + 1, sxssfSheet.getLastRowNum(), locationRecordsSize - 1); // 2. 复制行样式 for(int i = 1; i < locationRecordsSize; i++){ sxssfSheet.copyRows(tab1HeaderRowNum, tab1HeaderRowNum, tab1HeaderRowNum + i, cellCopyPolicy); } // 3. 流式处理Devices表(数据量大时自动刷盘,不影响前面的Locations行) int devicesStartRow = /* 计算Devices表的起始行位置 */; for (Device device : yourDevicesList) { Row row = sxssfSheet.createRow(devicesStartRow++); // 填充设备数据到单元格 row.createCell(0).setCellValue(device.getId()); row.createCell(1).setCellValue(device.getName()); // ...其他字段 } // 最后清理SXSSF的临时文件 sxssfWorkbook.dispose(); inputStream.close();
方案2:先读取模板内容到内存,再新建SXSSF写入(适合大数据量场景)
如果Locations表和Devices表的数据量都很大,直接用SXSSF缓存可能会导致内存溢出。这种情况下,你可以先把模板中Locations表的内容读取到内存,再新建SXSSFWorkbook从头写入,完全避免修改已刷盘行的问题:
FileInputStream inputStream = new FileInputStream(new File(filePath)); XSSFWorkbook templateWorkbook = new XSSFWorkbook(inputStream); XSSFSheet templateSheet = templateWorkbook.getSheet("Sheet1"); // 1. 把Locations表的表头和原有数据读取到内存 List<Row> locationTemplateRows = new ArrayList<>(); int tab1LastRowNum = /* Locations表的最后一行索引 */; for (int i = 0; i <= tab1LastRowNum; i++) { locationTemplateRows.add(templateSheet.getRow(i)); } // 2. 创建新的SXSSFWorkbook SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(); SXSSFSheet sxssfSheet = sxssfWorkbook.createSheet("Sheet1"); // 3. 写入Locations表头和扩展的空行(带样式) Row headerRow = sxssfSheet.createRow(tab1HeaderRowNum); // 复制表头的样式和内容 XSSFRow originalHeader = (XSSFRow) locationTemplateRows.get(tab1HeaderRowNum); for (Cell cell : originalHeader) { Cell newCell = headerRow.createCell(cell.getColumnIndex()); newCell.setCellStyle(cell.getCellStyle()); newCell.setCellValue(cell.getStringCellValue()); } // 写入locationRecordsSize行带样式的空行 for(int i = 1; i < locationRecordsSize; i++){ Row newRow = sxssfSheet.createRow(tab1HeaderRowNum + i); for (Cell cell : headerRow) { Cell newCell = newRow.createCell(cell.getColumnIndex()); newCell.setCellStyle(cell.getCellStyle()); } } // 4. 写入原来的Locations数据到移动后的位置 int dataStartRow = tab1HeaderRowNum + locationRecordsSize; for (int i = tab1HeaderRowNum + 1; i <= tab1LastRowNum; i++) { XSSFRow originalRow = (XSSFRow) locationTemplateRows.get(i); Row newRow = sxssfSheet.createRow(dataStartRow++); for (Cell cell : originalRow) { Cell newCell = newRow.createCell(cell.getColumnIndex()); newCell.setCellStyle(cell.getCellStyle()); // 根据单元格类型复制值 switch (cell.getCellType()) { case STRING: newCell.setCellValue(cell.getStringCellValue()); break; case NUMERIC: newCell.setCellValue(cell.getNumericCellValue()); break; // 按需处理其他类型(布尔、公式等) } } } // 5. 流式写入Devices表 int devicesStartRow = /* 计算Devices表的起始行 */; for (Device device : yourDevicesList) { Row row = sxssfSheet.createRow(devicesStartRow++); // 填充设备数据 } // 关闭资源 templateWorkbook.close(); inputStream.close(); sxssfWorkbook.dispose();
这两种方案都能解决你遇到的错误,方案1更简洁,方案2更适合大数据量场景,你可以根据自己的实际情况选择。
内容的提问来源于stack exchange,提问作者KDS
相关产品推荐
相关产品推荐

