You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多次调用workbook.write写入Excel首值缺失及保存错误问题

Apache POI: First Iteration Value Missing & Duplicate write() Stream Error

我来帮你分析并解决这两个和Apache POI写入Excel工作簿相关的问题,这些都是使用POI时很容易踩的坑:

问题回顾

你遇到了两个核心问题:

  1. 首次迭代值缺失:当仅在代码末尾调用一次owb.write(fileOut)和fileOut.close()时,程序无运行时错误,但首次k=1时设置的首值无法写入工作簿,其他列迭代k=1时正常,仅第一次迭代的这个值丢失,其余数据写入正常。
  2. 多次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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:58:00