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

相关推荐
椎4952 小时前
java微服务01-快速入门、mybatisplus
java·微服务
为什么不问问神奇的海螺呢丶2 小时前
n9e categraf k8s监控配置-n9e k8s监控看板
java·容器·kubernetes
浩浩测试一下2 小时前
内网---> ForceChangePassword 权限滥用
java·服务器·网络·安全·web安全·网络安全·系统安全
独自破碎E2 小时前
【BISHI11】变幻莫测
android·java·开发语言
qq_12498707532 小时前
基于Javaweb的《战舰世界》游戏百科信息系统(源码+论文+部署+安装)
java·vue.js·人工智能·spring boot·游戏·毕业设计·计算机毕业设计
梦想的旅途22 小时前
如何优雅地实现企微外部群消息自动化(Java/Python/Go 多语言版)
java·自动化·企业微信
短剑重铸之日2 小时前
《设计模式》第十篇:三大类型之行为型模式
java·后端·设计模式·责任链模式·访问者模式·行为型模式
应用市场2 小时前
踩坑记录:有符号整数位运算的那些隐蔽Bug——符号扩展、算术右移与补码
java·开发语言·bug
HWL56792 小时前
控制浏览器如何预先加载视频资源
java·服务器·前端