@qiao39gs
2025-05-29T06:58:24.000000Z
字数 8453
阅读 2
Okay, 对于“最快”的方法,我们通常会选择 Apache POI,因为它在读取 Excel 元数据(如行数)方面非常高效,特别是当你只需要行数而不需要读取单元格内容时。它通常直接从文件结构中读取这个信息,而不是逐行扫描。
另一个以性能著称的库是 EasyExcel (Alibaba),它在处理大型 Excel 文件时内存占用低且速度快,因为它基于 SAX 解析。但对于仅仅获取行数这个特定任务,Apache POI 的 getLastRowNum() 方法通常已经足够快,因为它不涉及加载整个工作表的数据到内存中。
结论先行:Apache POI 的 Sheet.getLastRowNum() 是获取“有数据或格式的最后一行”的索引的最常用且通常最快的方法。实际行数是 getLastRowNum() + 1(因为它是0基索引)。如果一个工作表完全为空,getLastRowNum() 可能会返回0或-1(取决于POI版本和具体情况),而 getPhysicalNumberOfRows() 会返回0。
下面是使用 Apache POI 的示例:
1. 添加 Maven 依赖 (pom.xml):
<dependencies><!-- Apache POI Core --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.5</version> <!-- 使用最新稳定版 --></dependency><!-- Apache POI OOXML (for .xlsx files) --><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.5</version> <!-- 使用最新稳定版 --></dependency></dependencies>
2. Java 代码:
import org.apache.poi.ss.usermodel.*;import org.apache.poi.xssf.usermodel.XSSFWorkbook; // For .xlsximport org.apache.poi.hssf.usermodel.HSSFWorkbook; // For .xlsimport java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;public class ExcelRowCountReader {/*** 获取指定 Excel 文件中指定 sheet 页的行数。* 这是最常用的方法,返回的是“最后有效行”的行号(0基)+ 1。** @param filePath Excel 文件路径* @param sheetName Sheet 页名称* @return 行数;如果 sheet 不存在或文件有问题,则返回 -1*/public static int getRowCountBySheetName(String filePath, String sheetName) {try (InputStream inp = new FileInputStream(filePath);Workbook workbook = WorkbookFactory.create(inp)) { // WorkbookFactory 能自动识别 .xls 和 .xlsxSheet sheet = workbook.getSheet(sheetName);if (sheet == null) {System.err.println("Sheet '" + sheetName + "' not found in " + filePath);return -1; // Sheet 不存在}// getLastRowNum() 返回最后一行的索引 (0-based)。// 如果工作表为空,或者只有第一行(索引为0)有数据,它可能会返回0。// 如果工作表完全为空且从未有过数据,HSSFSheet 可能返回 -1,XSSFSheet 返回 0。// getPhysicalNumberOfRows() 返回物理上存在的行数(即XML中定义的<row>标签数量),// 即使这些行是空的。如果一个sheet完全是空的(从未有过任何操作),它返回0。int lastRowNum = sheet.getLastRowNum();int physicalRows = sheet.getPhysicalNumberOfRows();// System.out.println("Sheet: " + sheetName + ", LastRowNum: " + lastRowNum + ", PhysicalNumberOfRows: " + physicalRows);if (physicalRows == 0) { // 如果物理行数为0,说明表是完全空的return 0;}// 否则,使用getLastRowNum() + 1 作为行数。// 对于只有一个空行或者第一行有数据的情况,lastRowNum是0,所以行数是1。return lastRowNum + 1;} catch (IOException e) {System.err.println("Error reading Excel file: " + e.getMessage());e.printStackTrace();return -1;} catch (Exception e) { // WorkbookFactory.create 可能会抛出其他异常,如 EncryptedDocumentExceptionSystem.err.println("Error processing Excel file: " + e.getMessage());e.printStackTrace();return -1;}}/*** 获取指定 Excel 文件中指定索引的 sheet 页的行数。** @param filePath Excel 文件路径* @param sheetIndex Sheet 页索引 (0-based)* @return 行数;如果 sheet 不存在或文件有问题,则返回 -1*/public static int getRowCountBySheetIndex(String filePath, int sheetIndex) {try (InputStream inp = new FileInputStream(filePath);Workbook workbook = WorkbookFactory.create(inp)) {if (sheetIndex < 0 || sheetIndex >= workbook.getNumberOfSheets()) {System.err.println("Sheet index " + sheetIndex + " is out of bounds.");return -1;}Sheet sheet = workbook.getSheetAt(sheetIndex);if (sheet == null) { // 理论上,如果索引有效,这里不应该为 nullSystem.err.println("Sheet at index " + sheetIndex + " not found (should not happen if index is valid).");return -1;}int lastRowNum = sheet.getLastRowNum();int physicalRows = sheet.getPhysicalNumberOfRows();// System.out.println("Sheet Index: " + sheetIndex + ", LastRowNum: " + lastRowNum + ", PhysicalNumberOfRows: " + physicalRows);if (physicalRows == 0) {return 0;}return lastRowNum + 1;} catch (IOException e) {System.err.println("Error reading Excel file: " + e.getMessage());e.printStackTrace();return -1;} catch (Exception e) {System.err.println("Error processing Excel file: " + e.getMessage());e.printStackTrace();return -1;}}public static void main(String[] args) {// 创建一个临时的测试Excel文件 (你可以替换成你自己的文件路径)String testFilePathXlsx = "test_workbook.xlsx";String testFilePathXls = "test_workbook.xls";// 创建 XLSX 文件try (Workbook workbook = new XSSFWorkbook()) {Sheet sheet1 = workbook.createSheet("DataSheet1");sheet1.createRow(0).createCell(0).setCellValue("Header1");sheet1.createRow(1).createCell(0).setCellValue("Data1");sheet1.createRow(4).createCell(0).setCellValue("Data_in_row_5"); // 第5行 (索引4)Sheet sheet2 = workbook.createSheet("EmptySheet"); // 完全空的Sheet sheet3 = workbook.createSheet("SingleRowSheet");sheet3.createRow(0).createCell(0).setCellValue("OnlyRow");try (java.io.FileOutputStream fos = new java.io.FileOutputStream(testFilePathXlsx)) {workbook.write(fos);}} catch (IOException e) {e.printStackTrace();}// 创建 XLS 文件 (HSSFWorkbook)try (Workbook workbook = new HSSFWorkbook()) {Sheet sheet1 = workbook.createSheet("DataSheet1_xls");sheet1.createRow(0).createCell(0).setCellValue("Header1_xls");sheet1.createRow(1).createCell(0).setCellValue("Data1_xls");sheet1.createRow(9).createCell(0).setCellValue("Data_in_row_10_xls"); // 第10行 (索引9)try (java.io.FileOutputStream fos = new java.io.FileOutputStream(testFilePathXls)) {workbook.write(fos);}} catch (IOException e) {e.printStackTrace();}System.out.println("--- Testing XLSX (" + testFilePathXlsx + ") ---");int rowCount1 = getRowCountBySheetName(testFilePathXlsx, "DataSheet1");System.out.println("Rows in DataSheet1 (by name): " + rowCount1); // 应该输出 5int rowCount2 = getRowCountBySheetName(testFilePathXlsx, "EmptySheet");System.out.println("Rows in EmptySheet (by name): " + rowCount2); // 应该输出 0int rowCount3 = getRowCountBySheetName(testFilePathXlsx, "NonExistentSheet");System.out.println("Rows in NonExistentSheet (by name): " + rowCount3); // 应该输出 -1 (并打印错误)int rowCount4 = getRowCountBySheetIndex(testFilePathXlsx, 0);System.out.println("Rows in Sheet at index 0 (by index): " + rowCount4); // 应该输出 5 (DataSheet1)int rowCount5 = getRowCountBySheetIndex(testFilePathXlsx, 2);System.out.println("Rows in Sheet at index 2 (by index): " + rowCount5); // 应该输出 1 (SingleRowSheet)System.out.println("\n--- Testing XLS (" + testFilePathXls + ") ---");int rowCountXls = getRowCountBySheetName(testFilePathXls, "DataSheet1_xls");System.out.println("Rows in DataSheet1_xls (by name): " + rowCountXls); // 应该输出 10// 清理测试文件// new File(testFilePathXlsx).delete();// new File(testFilePathXls).delete();}}
解释和为什么这通常是“最快”的:
WorkbookFactory.create(inp): 这是一个方便的方法,它可以自动检测文件是 .xls (HSSF) 还是 .xlsx (XSSF) 格式,并创建相应的 Workbook 对象。workbook.getSheet(sheetName) 或 workbook.getSheetAt(sheetIndex): 获取对特定工作表的引用。这个操作本身很快。sheet.getLastRowNum(): .xlsx (XSSF),它通常解析 xl/worksheets/sheetN.xml 文件中的 <dimension ref="A1:C10"/> 这样的标签,直接获取范围。.xls (HSSF),它会查找内部记录来确定最后一行的位置。sheet.getPhysicalNumberOfRows(): <row> 元素的数量。一个行可以被定义但完全是空的。<row>标签),这个方法会返回0。getPhysicalNumberOfRows() 返回 0,那么工作表肯定是空的,行数为 0。getLastRowNum() + 1。 getLastRowNum() 返回 0(意味着最后有数据的行是第一行,索引为0),那么行数是 0 + 1 = 1。getLastRowNum() 可能返回 -1,此时 -1 + 1 = 0,这也是正确的。较新版本的 POI 对于 XSSF 空表返回 0。if (physicalRows == 0) return 0; else return lastRowNum + 1; 是一个比较稳妥的逻辑。关于EasyExcel:
EasyExcel 在读取大量数据时非常快且内存友好,因为它使用 SAX 解析器逐步读取。但如果你仅仅需要行数,它也需要解析XML结构来找到行。
// 仅作概念演示,获取行数通常不这么用EasyExcelimport com.alibaba.excel.EasyExcel;import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.read.listener.ReadListener;import com.alibaba.excel.metadata.CellData; // 如果需要用CellDataimport com.alibaba.excel.metadata.data.ReadCellData; // EasyExcel 3.ximport java.util.Map;import java.util.concurrent.atomic.AtomicInteger;// ... (假设你已添加 EasyExcel 依赖)public class EasyExcelRowCount {public static int getRowCountWithEasyExcel(String filePath, String sheetName) {AtomicInteger maxRowIndex = new AtomicInteger(-1); // 用 AtomicInteger 确保线程安全(虽然这里单线程)try {EasyExcel.read(filePath, new ReadListener<Map<Integer, String>>() {@Overridepublic void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {// EasyExcel 3.x 使用 ReadCellData// 如果有表头,当前行索引是0if (!headMap.isEmpty()) {maxRowIndex.set(Math.max(maxRowIndex.get(), context.readRowHolder().getRowIndex()));}}@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {// 任何数据行都会调用这里maxRowIndex.set(Math.max(maxRowIndex.get(), context.readRowHolder().getRowIndex()));}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 所有行读取完毕}}).sheet(sheetName).doRead(); // 或者 .sheet(sheetIndex)// maxRowIndex 现在是0基的最大行索引if (maxRowIndex.get() == -1) { // 如果没有调用过 invoke 或 invokeHead// 尝试获取近似总行数 (但这可能不精确,或在某些情况下不可用)// Integer approximateTotalRowNumber = context.readSheetHolder().getApproximateTotalRowNumber();// 对于精确计数,还是依赖 invoke/invokeHead 更好return 0;}return maxRowIndex.get() + 1;} catch (Exception e) {e.printStackTrace();return -1;}}// main 方法中调用 EasyExcelRowCount.getRowCountWithEasyExcel(...)}
对于仅仅获取行数,EasyExcel 的方式会显得有点“重”,因为它设计的目标是流式处理数据。AnalysisContext 中的 getApproximateTotalRowNumber() 可能有用,但它不总是精确的。所以,为了精确行数,你还是得让它至少把行结构读一遍。
总结:
对于“获取指定sheet页的行数”这个特定且简单的任务,Apache POI 的 sheet.getLastRowNum() 加上适当的逻辑判断,通常是最直接且性能足够高的方法。它不需要完整的数据解析,而是利用了文件结构中的元信息。