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 ""; } }
最后注意事项
- 如果你是基于
Prod_Rel.xlsx复制得到的结果文件,记得在打开时就用SXSSFWorkbook包装,比如:
FileInputStream fis = new FileInputStream("Prod_Rel.xlsx"); SXSSFWorkbook resultWorkbook = new SXSSFWorkbook(new XSSFWorkbook(fis), 100);
- 写入文件时,使用
FileOutputStream即可,写完后记得关闭流并调用dispose():
FileOutputStream fos = new FileOutputStream("对比结果.xlsx"); resultWorkbook.write(fos); fos.close(); resultWorkbook.dispose(); // 释放临时文件
内容的提问来源于stack exchange,提问作者ardjavatake2selenium
相关产品推荐
相关产品推荐

