多次调用workbook.write写入Excel首值缺失及保存错误问题
write() Stream Error 我来帮你分析并解决这两个和Apache POI写入Excel工作簿相关的问题,这些都是使用POI时很容易踩的坑:
问题回顾
你遇到了两个核心问题:
- 首次迭代值缺失:当仅在代码末尾调用一次
owb.write(fileOut)和fileOut.close()时,程序无运行时错误,但首次k=1时设置的首值无法写入工作簿,其他列迭代k=1时正常,仅第一次迭代的这个值丢失,其余数据写入正常。 - 多次
write()导致流错误:尝试在if(标注为if condition[1])和else(标注为else condition [2])分支中分别调用owb.write(fileOut)时,出现运行时错误:Fail to save: an error occurs while saving the package : The part /docProps/app.xml fail to be saved in the stream with marshaller org.apache.poi.openxml4j.opc.internal.marshallers.DefaultMarshaller@3740f768
附上你的代码片段:
for(int i=0;i<noOfCols1;i++) { for(int j=1;j<=noOfRows1;j++) { value1 = formatter.formatCellValue(sheet1.getRow(j).getCell(i)); for(int m=1;m<=noOfRows2;m++) { value2 = formatter.formatCellValue(sheet2.getRow(m).getCell(i)); value1= value1.trim(); value2= value2.trim(); int value2Position = sheet2.getRow(m).getCell(i).getRowIndex(); if(!positions.contains(value2Position)) { if(value1.contentEquals(value2)) { positions.add(value2Position); matched = true; } else{ matched = false; } } if(matched==true) { break; } } if(matched == false) { int k=1; if(cFilledPositions.isEmpty()) //If condition[i] { rowHead = sheet.createRow((short)k); rowHead.createCell(i).setCellValue(value1); owb.write(fileOut); //[1] } else //else condition [1] { int l = cFilledPositions.size()-1; k = cFilledPositions.get(l)+1; rowHead = sheet.createRow((short)k); rowHead.createCell(i).setCellValue(value1); owb.write(fileOut); } cFilledPositions.add(k); } matched = false; } cFilledPositions.clear(); positions.clear(); } //write and close positioning fileOut.close();
问题原因分析
1. 首次迭代值缺失的原因
当你只在末尾调用一次write()时,问题出在POI对Excel对象的内存对象树序列化时机。首次创建行sheet.createRow((short)k)并设置单元格值后,POI内部的工作簿对象可能还没完成完整的元数据初始化,或者后续循环中cFilledPositions.clear()的操作,导致首次写入的行标记在内存中没有被正确保留到最终序列化阶段。简单来说,就是内存中的对象状态在循环过程中被修改,导致首次值没有被纳入最终的写入队列。
2. 多次write()导致流错误的原因
Apache POI的XSSFWorkbook.write(OutputStream)方法绝对不能被多次调用!第一次调用write()时,POI会将整个OPC包(Excel的底层打包格式)完整写入输出流,同时会关闭内部的一些资源、标记包为已写入状态。第二次调用时,像/docProps/app.xml这类元数据部分已经被序列化过,再次尝试写入会触发资源冲突,导致报错。
解决方案:正确的写入流程
我们需要遵循POI的最佳实践:所有数据操作都在内存中完成,仅在最后一次性写入并关闭流,同时调整逻辑确保首次值被正确保留。
修改后的代码示例:
// 注意:提前初始化输出流,放在循环外部 FileOutputStream fileOut = new FileOutputStream("your-target-file.xlsx"); for(int i=0;i<noOfCols1;i++) { for(int j=1;j<=noOfRows1;j++) { value1 = formatter.formatCellValue(sheet1.getRow(j).getCell(i)); for(int m=1;m<=noOfRows2;m++) { value2 = formatter.formatCellValue(sheet2.getRow(m).getCell(i)); value1= value1.trim(); value2= value2.trim(); int value2Position = sheet2.getRow(m).getCell(i).getRowIndex(); if(!positions.contains(value2Position)) { if(value1.contentEquals(value2)) { positions.add(value2Position); matched = true; } else{ matched = false; } } if(matched==true) { break; } } if(matched == false) { int k=1; if(cFilledPositions.isEmpty()) //If condition[i] { rowHead = sheet.createRow(k); // 建议用int类型索引,避免short溢出 rowHead.createCell(i).setCellValue(value1); // 移除循环内的write调用,所有操作在内存完成 } else //else condition [1] { int l = cFilledPositions.size()-1; k = cFilledPositions.get(l)+1; rowHead = sheet.createRow(k); rowHead.createCell(i).setCellValue(value1); // 移除循环内的write调用 } cFilledPositions.add(k); } matched = false; } cFilledPositions.clear(); positions.clear(); } // 所有数据处理完成后,一次性写入并关闭资源 try { owb.write(fileOut); } finally { // 确保流被关闭,即使写入出错 fileOut.close(); // 关闭工作簿,释放POI内部资源 owb.close(); }
额外优化建议
- 添加空值检查:比如
sheet1.getRow(j)或sheet1.getRow(j).getCell(i)可能为null,避免空指针异常 - 避免使用
short类型的行索引:POI现在支持int类型,更安全,不会出现索引溢出问题 - 可以考虑使用
SXSSFWorkbook代替XSSFWorkbook,如果处理的是大数据量,避免内存溢出
内容的提问来源于stack exchange,提问作者Gaurav Thantry

