Apache POI将制表符分隔TXT当作XLSX处理的异常问题咨询
解决思路:无法直接将TXT当作XSSFWorkbook处理,但可以复用对比逻辑
首先得明确:你没法直接把制表符分隔的TXT文件当作XSSFWorkbook来处理——XSSFWorkbook是Apache POI专门为OOXML格式(也就是标准的.xlsx文件)设计的解析器,而TXT是纯文本格式,和OOXML的压缩二进制结构完全不兼容,所以抛出NotOfficeXmlFileException是必然的结果。
但你的核心需求是复用现有的XLSX对比逻辑,不用单独写TXT的对比代码,这个是可以实现的,核心思路是抽象统一的数据访问层,让对比逻辑只和通用的“工作表”接口交互,不用关心底层是XLSX还是TXT。
具体实现步骤
1. 定义通用的工作表访问接口
先写一个接口,封装获取行数、列数、单元格值的核心方法,让XLSX和TXT的读取逻辑都实现这个接口:
public interface SheetAccessor { // 获取总行数 int getRowCount(); // 获取指定行的列数 int getColumnCount(int rowIndex); // 获取指定行、列的单元格值(统一返回字符串,方便对比) String getCellValue(int rowIndex, int columnIndex); }
2. 为XLSX实现接口
直接复用你现有的XSSFWorkbook逻辑,把它包装到接口实现里:
import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class XlsxSheetAccessor implements SheetAccessor { private XSSFSheet sheet; public XlsxSheetAccessor(File xlsxFile) throws IOException { try (XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(xlsxFile))) { // 假设只处理第一个工作表,你可以根据需求调整 this.sheet = workbook.getSheetAt(0); } } @Override public int getRowCount() { return sheet.getLastRowNum() + 1; // 注意POI的行索引从0开始,LastRowNum是最后一行的索引 } @Override public int getColumnCount(int rowIndex) { XSSFRow row = sheet.getRow(rowIndex); return row != null ? row.getLastCellNum() : 0; } @Override public String getCellValue(int rowIndex, int columnIndex) { XSSFRow row = sheet.getRow(rowIndex); if (row == null) return ""; XSSFCell cell = row.getCell(columnIndex); if (cell == null) return ""; // 统一把单元格值转成字符串,方便对比(可根据你的需求调整格式,比如日期的显示方式) switch (cell.getCellType()) { case STRING: return cell.getStringCellValue(); case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { return cell.getDateCellValue().toString(); } else { return String.valueOf(cell.getNumericCellValue()); } case BOOLEAN: return String.valueOf(cell.getBooleanCellValue()); default: return ""; } } }
3. 为制表符TXT实现接口
读取TXT文件,按行拆分,再按制表符拆分单元格,同样实现接口:
import java.io.File; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; import java.nio.file.Files; public class TsvSheetAccessor implements SheetAccessor { private List<String[]> rows; public TsvSheetAccessor(File tsvFile) throws IOException { // 读取所有行,按制表符拆分单元格(-1参数保留末尾的空制表符,避免丢失空列) rows = Files.lines(tsvFile.toPath()) .map(line -> line.split("\t", -1)) .collect(Collectors.toList()); } @Override public int getRowCount() { return rows.size(); } @Override public int getColumnCount(int rowIndex) { if (rowIndex < 0 || rowIndex >= rows.size()) return 0; return rows.get(rowIndex).length; } @Override public String getCellValue(int rowIndex, int columnIndex) { if (rowIndex < 0 || rowIndex >= rows.size()) return ""; String[] row = rows.get(rowIndex); if (columnIndex < 0 || columnIndex >= row.length) return ""; return row[columnIndex]; } }
4. 修改对比逻辑,复用核心代码
把你原来的XLSX对比逻辑改成接收SheetAccessor参数,这样不管是XLSX还是TXT,对比逻辑完全一样:
import java.util.ArrayList; import java.util.List; public class SheetComparator { public List<String> compareSheets(SheetAccessor controlSheet, SheetAccessor testSheet) { List<String> mismatches = new ArrayList<>(); // 取两个表的最大行数和最大列数,避免遗漏行/列 int maxRows = Math.max(controlSheet.getRowCount(), testSheet.getRowCount()); for (int row = 0; row < maxRows; row++) { int maxCols = Math.max(controlSheet.getColumnCount(row), testSheet.getColumnCount(row)); for (int col = 0; col < maxCols; col++) { String controlVal = controlSheet.getCellValue(row, col); String testVal = testSheet.getCellValue(row, col); if (!controlVal.equals(testVal)) { // 这里的行号列号加1,是因为用户习惯从1开始计数 mismatches.add(String.format("不匹配:行%d,列%d → 控制文件值='%s',测试文件值='%s'", row + 1, col + 1, controlVal, testVal)); } } } return mismatches; } }
5. 调用时自动适配文件类型
最后在主逻辑里,根据文件后缀判断用哪个SheetAccessor实现:
import java.io.File; import java.io.IOException; import java.util.List; public class Main { public static void main(String[] args) throws IOException { File controlFile = new File("path/to/control/file"); File testFile = new File("path/to/test/file"); SheetAccessor controlSheet = createSheetAccessor(controlFile); SheetAccessor testSheet = createSheetAccessor(testFile); SheetComparator comparator = new SheetComparator(); List<String> results = comparator.compareSheets(controlSheet, testSheet); // 输出结果 for (String mismatch : results) { System.out.println(mismatch); } } private static SheetAccessor createSheetAccessor(File file) throws IOException { String fileName = file.getName().toLowerCase(); if (fileName.endsWith(".xlsx")) { return new XlsxSheetAccessor(file); } else if (fileName.endsWith(".txt")) { return new TsvSheetAccessor(file); } else { throw new IllegalArgumentException("不支持的文件类型:" + fileName); } } }
额外说明:要不要把TXT转成XSSFWorkbook?
如果你非要“把TXT当作XSSFWorkbook处理”,理论上可以读取TXT内容后,在内存中创建一个XSSFWorkbook,逐行逐单元格写入数据,但这样完全是额外的内存开销,没有任何必要——上面的接口方式更高效,而且完全复用了你的对比逻辑,是更优的解决方案。
内容的提问来源于stack exchange,提问作者WhitneyChia
相关产品推荐
相关产品推荐

