Excel文件解析 - SAX startRow cell endRow 执行时机

好的!让我用更简洁直观的方式说明这三个方法的执行时机。

执行时机总结

复制代码
Excel 文件结构:
┌─────────────────────────────────┐
│  第 1 行                         │
│  ┌───┬───┬───┐                │
│  │A1 │B1 │C1 │                │
│  └───┴───┴───┘                │
├─────────────────────────────────┤
│  第 2 行                         │
│  ┌───┬───┬───┐                │
│  │A2 │B2 │C2 │                │
│  └───┴───┴───┘                │
└─────────────────────────────────┘

解析流程:

第 1 行:
  ↓
  startRow(0)  ← 遇到第 1 行开始
  ↓
  cell("A1", "...")  ← 遇到 A1 单元格
  ↓
  cell("B1", "...")  ← 遇到 B1 单元格
  ↓
  cell("C1", "...")  ← 遇到 C1 单元格
  ↓
  endRow(0)  ← 遇到第 1 行结束
  ↓
第 2 行:
  ↓
  startRow(1)  ← 遇到第 2 行开始
  ↓
  cell("A2", "...")  ← 遇到 A2 单元格
  ↓
  cell("B2", "...")  ← 遇到 B2 单元格
  ↓
  cell("C2", "...")  ← 遇到 C2 单元格
  ↓
  endRow(1)  ← 遇到第 2 行结束
  ↓
... 继续下一行

三个方法的触发条件

1. startRow(int rowNum)

触发时间:

  • 当 SAX 解析器遇到 <row> 标签时

触发条件:

  • ✅ 每一行的开始都会触发
  • ✅ 包括空行
  • ✅ 表头行也会触发

示例:

java 复制代码
// Excel 第 1 行开始
startRow(0)  // ← 触发

// Excel 第 2 行开始
startRow(1)  // ← 触发

// Excel 第 3 行(空行)开始
startRow(2)  // ← 仍然触发

2. cell(String cellReference, String formattedValue, XSSFComment comment)

触发时间:

  • 当 SAX 解析器遇到 <c> 标签时

触发条件:

  • ✅ 每个有数据的单元格都会触发
  • ✅ 空单元格(有值标签但值为空)也会触发
  • ❌ 完全没有值标签的单元格不会触发

示例:

java 复制代码
// 第 1 行的单元格
cell("A1", "姓名", null)  // ← 触发
cell("B1", "年龄", null)  // ← 触发
cell("C1", "邮箱", null)  // ← 触发

// 第 2 行的单元格
cell("A2", "张三", null)  // ← 触发
cell("B2", "25", null)   // ← 触发
cell("C2", "test@qq.com", null)  // ← 触发

// 第 3 行(空行)
// 没有任何 cell 被触发

3. endRow(int rowNum)

触发时间:

  • 当 SAX 解析器遇到 </row> 标签时

触发条件:

  • ✅ 每一行的结束都会触发
  • ✅ 包括空行
  • ✅ 表头行也会触发

示例:

java 复制代码
// Excel 第 1 行结束
endRow(0)  // ← 触发

// Excel 第 2 行结束
endRow(1)  // ← 触发

// Excel 第 3 行(空行)结束
endRow(2)  // ← 仍然触发

完整的执行顺序示例

示例 Excel

A B C
1 姓名 年龄 邮箱
2 张三 25 test@qq.com
3
4 李四 30 test2@qq.com

执行顺序

复制代码
时间轴:
t1:  startRow(0)           // 第 1 行开始
t2:  cell("A1", "姓名", null)
t3:  cell("B1", "年龄", null)
t4:  cell("C1", "邮箱", null)
t5:  endRow(0)             // 第 1 行结束

t6:  startRow(1)           // 第 2 行开始
t7:  cell("A2", "张三", null)
t8:  cell("B2", "25", null)
t9:  cell("C2", "test@qq.com", null)
t10: endRow(1)             // 第 2 行结束

t11: startRow(2)           // 第 3 行开始(空行)
t12: endRow(2)             // 第 3 行结束
     // 注意:没有 cell 调用,因为第 3 行是空行

t13: startRow(3)           // 第 4 行开始
t14: cell("A4", "李四", null)
t15: cell("B4", "30", null)
t16: cell("C4", "test2@qq.com", null)
t17: endRow(3)             // 第 4 行结束

图解

正常行(有数据)

复制代码
Excel: ┌───┬───┬───┐
       │A1 │B1 │C1 │
       └───┴───┴───┘

解析:
┌─────────────────────────────────────┐
│ startRow(0)  ← 遇到 <row>         │
│     ↓                              │
│ cell("A1", "...")  ← 遇到 <c>     │
│     ↓                              │
│ cell("B1", "...")  ← 遇到 <c>     │
│     ↓                              │
│ cell("C1", "...")  ← 遇到 <c>     │
│     ↓                              │
│ endRow(0)  ← 遇到 </row>          │
└─────────────────────────────────────┘

空行(没有数据)

复制代码
Excel: ┌───┬───┬───┐
       │   │   │   │  ← 空行
       └───┴───┴───┘

解析:
┌─────────────────────────────────────┐
│ startRow(2)  ← 遇到 <row>         │
│     ↓                              │
│     (没有 cell 调用)                │
│     ↓                              │
│ endRow(2)  ← 遇到 </row>          │
└─────────────────────────────────────┘

关键点总结

方法 触发时机 触发频率 是否包括空行
startRow 行开始 每行一次 ✅ 是
cell 单元格 每个单元格一次 ❌ 否(空行没有)
endRow 行结束 每行一次 ✅ 是

实际代码示例

java 复制代码
private static class DemoHandler implements XSSFSheetXMLHandler.SheetContentsHandler {
    private int rowNum = 0;
    private int cellCount = 0;
    
    @Override
    public void startRow(int rowNum) {
        System.out.println("📍 第 " + (rowNum + 1) + " 行开始");
        this.rowNum = rowNum;
    }
    
    @Override
    public void cell(String cellReference, String formattedValue, XSSFComment comment) {
        cellCount++;
        System.out.println("  📦 单元格 " + cellReference + ": " + formattedValue);
    }
    
    @Override
    public void endRow(int rowNum) {
        System.out.println("🏁 第 " + (rowNum + 1) + " 行结束(共 " + cellCount + " 个单元格)");
        cellCount = 0;  // 重置单元格计数
    }
    
    @Override
    public void headerFooter(String text, boolean isHeader, String tagName) {
        // 忽略
    }
}

输出示例:

复制代码
📍 第 1 行开始
  📦 单元格 A1: 姓名
  📦 单元格 B1: 年龄
  📦 单元格 C1: 邮箱
🏁 第 1 行结束(共 3 个单元格)

📍 第 2 行开始
  📦 单元格 A2: 张三
  📦 单元格 B2: 25
  📦 单元格 C2: test@qq.com
🏁 第 2 行结束(共 3 个单元格)

📍 第 3 行开始
🏁 第 3 行结束(共 0 个单元格)

📍 第 4 行开始
  📦 单元格 A4: 李四
  📦 单元格 B4: 30
  📦 单元格 C4: test2@qq.com
🏁 第 4 行结束(共 3 个单元格)

一句话总结

  • startRow:每行开始时调用一次
  • cell:每个单元格调用一次(在 startRow 和 endRow 之间)
  • endRow:每行结束时调用一次

执行顺序: startRowcell (多次) → endRow

相关推荐
日月云棠17 小时前
各版本JDK对比:JDK 25 特性详解
java
用户83071968408218 小时前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
JavaGuide19 小时前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
IT探险家19 小时前
Java 基本数据类型:8 种原始类型 + 数组 + 6 个新手必踩的坑
java
花花无缺19 小时前
搞懂new 关键字(构造函数)和 .builder() 模式(建造者模式)创建对象
java
用户9083246027319 小时前
Spring Boot + MyBatis-Plus 多租户实战:从数据隔离到权限控制的完整方案
java·后端
桦说编程20 小时前
实战分析 ConcurrentHashMap.computeIfAbsent 的锁冲突问题
java·后端·性能优化
程序员清风1 天前
用了三年AI,我总结出高效使用AI的3个习惯!
java·后端·面试
beata1 天前
Java基础-13: Java反射机制详解:原理、使用与实战示例
java·后端
用户0332126663671 天前
Java 使用 Spire.Presentation 在 PowerPoint 中添加或删除表格行与列
java