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

Java超4万行Excel单元格对比及差异高亮(空指针问题排查)

解决SXSSFWorkbook设置行样式时的NPE问题及大文件对比优化

首先,你遇到的NullPointerException大概率是因为结果工作表中对应的行不存在——调用sheetResult.getRow(rowNum)时,如果该行从未被创建过(比如原Prod_Rel.xlsx里该行没有任何数据),会返回null,直接调用setRowStyle自然会抛出空指针异常。

接下来我会一步步给你解决这个问题,同时优化大文件的对比和写入逻辑:


1. 先解决NPE:确保结果行存在

在设置行样式之前,必须先检查行是否为null,如果是则创建该行:

// 获取结果行,如果不存在则创建
Row row3edit = sheetResult.getRow(rowNum);
if (row3edit == null) {
    row3edit = sheetResult.createRow(rowNum);
}
// 现在再设置行样式就不会NPE了
row3edit.setRowStyle(cellStyleRed);

不过这里要提醒你:SXSSF中设置整行样式并不推荐——因为SXSSF的刷盘机制(将内存中超出阈值的行写入临时文件)可能会导致行样式丢失或失效,而且如果只有部分单元格有差异,整行高亮也不够精准。更可靠的方式是给差异单元格单独设置样式。


2. SXSSF样式的正确创建方式

你提到用XSSFCellStyle创建样式后转换使用,其实更规范的做法是直接从结果文件的SXSSFWorkbook创建样式(避免跨Workbook引用样式的问题):

// 从结果文件的SXSSFWorkbook创建红色高亮样式
SXSSFWorkbook resultWorkbook = new SXSSFWorkbook(100); // 保持100行内存阈值
CellStyle redStyle = resultWorkbook.createCellStyle();
// 设置填充颜色为红色
redStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
redStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

// 如果需要保留原单元格的原有样式(比如字体、对齐方式),可以先克隆原单元格样式再修改:
// CellStyle redStyle = originalCell.cloneCellStyle();
// redStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
// redStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

这样创建的样式可以直接在SXSSF的单元格上使用,不会有兼容性问题。


3. 大文件对比写入的优化方案

针对4万行级别的Excel,结合SXSSF的特性,推荐以下优化点:

  • 复用样式,避免重复创建:Excel对单个文件的样式数量有上限(约65535个),循环中创建样式会很快触发异常,所以提前创建好需要的样式(比如红色高亮),在对比时直接复用即可。
  • 给差异单元格单独设置样式:相比整行样式,单元格样式在SXSSF刷盘时更稳定,而且能精准标记差异位置。
  • 减少不必要的对象创建:循环中不要重复调用getSheet(),提前将三个工作表(原两个、结果表)赋值给变量;获取单元格时先判断行是否为null,避免空指针。
  • 务必释放临时文件:SXSSF会生成临时文件存储超出内存阈值的行,写入完成后必须调用resultWorkbook.dispose()释放这些文件,避免磁盘空间浪费。

完整的核心对比代码示例

private static void compareTwoSheets(SXSSFSheet sheet1, SXSSFSheet sheet2, SXSSFSheet resultSheet, CellStyle redHighlightStyle) {
    // 获取最大行数,覆盖两个工作表的所有行
    int maxRowNum = Math.max(sheet1.getLastRowNum(), sheet2.getLastRowNum());

    for (int rowIndex = 0; rowIndex <= maxRowNum; rowIndex++) {
        SXSSFRow row1 = sheet1.getRow(rowIndex);
        SXSSFRow row2 = sheet2.getRow(rowIndex);
        SXSSFRow resultRow = resultSheet.getRow(rowIndex);

        // 确保结果行存在
        if (resultRow == null) {
            resultRow = resultSheet.createRow(rowIndex);
        }

        // 获取最大列数,覆盖两个行的所有列
        int maxColNum = Math.max(
                row1 != null ? row1.getLastCellNum() : 0,
                row2 != null ? row2.getLastCellNum() : 0
        );

        for (int colIndex = 0; colIndex < maxColNum; colIndex++) {
            SXSSFCell cell1 = row1 != null ? row1.getCell(colIndex) : null;
            SXSSFCell cell2 = row2 != null ? row2.getCell(colIndex) : null;
            SXSSFCell resultCell = resultRow.getCell(colIndex);

            // 确保结果单元格存在
            if (resultCell == null) {
                resultCell = resultRow.createCell(colIndex);
            }

            // 获取单元格的字符串值用于对比
            String value1 = getCellStringValue(cell1);
            String value2 = getCellStringValue(cell2);

            // 对比值,不一致则设置红色高亮
            if (!Objects.equals(value1, value2)) {
                resultCell.setCellStyle(redHighlightStyle);
            }
        }
    }
}

// 辅助方法:将单元格转换为统一的字符串值,兼容不同数据类型
private static String getCellStringValue(Cell cell) {
    if (cell == null) {
        return "";
    }
    switch (cell.getCellType()) {
        case STRING:
            return cell.getStringCellValue().trim();
        case NUMERIC:
            if (DateUtil.isCellDateFormatted(cell)) {
                return cell.getDateCellValue().toString();
            } else {
                // 处理数字精度问题,比如整数转字符串避免小数点
                double numericValue = cell.getNumericCellValue();
                if (numericValue == (long) numericValue) {
                    return String.valueOf((long) numericValue);
                } else {
                    return String.valueOf(numericValue);
                }
            }
        case BOOLEAN:
            return String.valueOf(cell.getBooleanCellValue());
        case FORMULA:
            return cell.getCellFormula();
        default:
            return "";
    }
}

最后注意事项

  1. 如果你是基于Prod_Rel.xlsx复制得到的结果文件,记得在打开时就用SXSSFWorkbook包装,比如:
FileInputStream fis = new FileInputStream("Prod_Rel.xlsx");
SXSSFWorkbook resultWorkbook = new SXSSFWorkbook(new XSSFWorkbook(fis), 100);
  1. 写入文件时,使用FileOutputStream即可,写完后记得关闭流并调用dispose():
FileOutputStream fos = new FileOutputStream("对比结果.xlsx");
resultWorkbook.write(fos);
fos.close();
resultWorkbook.dispose(); // 释放临时文件

内容的提问来源于stack exchange,提问作者ardjavatake2selenium

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 16:24:14