< skus>
< skuid= "345000">
< name> 电脑A </ name>
< price> 5999.0 </ price>
</ sku>
< skuid= "345001">
< name> 手机C </ name>
< price> 4599.0 </ price>
</ sku>
</ skus>
对照 XML 结构,创建 Java 实体类:
importlombok.Data;
@Data
publicclassSku{
privateLongid;
privateString name;
privateDoubleprice;
}
自定义事件处理类 SkuHandler:
importcom.alibaba.fastjson.JSON;
importorg.shy.domain.pojo.Sku;
importorg.xml.sax.Attributes;
importorg.xml.sax.SAXException;
importorg.xml.sax.helpers.DefaultHandler;
publicclassSkuHandler extendsDefaultHandler {
/**
* 当前正在处理的sku
*/
privateSku sku;
/**
* 当前正在处理的节点名称
*/
privateStringtagName;
@Override
publicvoidstartElement( Stringuri, StringlocalName, StringqName, Attributes attributes) throws SAXException {
if( "sku".equals(qName)) {
sku = newSku;
sku.setId(Long.valueOf((attributes.getValue( "id"))));
}
tagName = qName;
}
@Override
publicvoidendElement( Stringuri, StringlocalName, StringqName) throws SAXException {
if( "sku".equals(qName)) {
System.out.println( JSON.toJSONString(sku));
// 处理业务逻辑
// ...
}
tagName = null;
}
@Override
publicvoidcharacters(char[] ch, int start, int length) throws SAXException {
if( "name".equals(tagName)) {
sku.setName( newString(ch, start, length));
}
if( "price".equals(tagName)) {
sku.setPrice(Double.valueOf( newString(ch, start, length)));
}
}
}
其中,SkuHandler 重写了三个事件响应方法:
startElement—— 每当扫描到新 XML 元素时,调用此方法,传入 XML 标签名称 qName,XML 属性列表 attributes;
characters—— 每当扫描到未在 XML 标签中的字符串时,调用此方法,传入字符数组、起始下标和长度;
endElement—— 每当扫描到 XML 元素的结束标签时,调用此方法,传入 XML 标签名称 qName 。
我们用一个变量 tagName 存储当前扫描到的节点信息,每次扫描节点发送变化时,更新 tagName;
用一个 Sku 实例维护当前读入内存的 Sku 信息,每当该 Sku 读取完成时,我们打印该 Sku 信息,并执行相应业务逻辑 。这样,就可以做到一次读取一条 Sku 信息,边解析边处理 。由于每行 Sku 结构相同,因此,只需要在内存维护一条 Sku 信息即可,避免了一次性把所有信息读入内存 。
调用 SAX 解析器时,使用 SAXParserFactory 创建解析器实例,解析输入流即可,Main 方法如下:
importorg.shy.xlsx.sax.handler.SkuHandler;
importjavax.xml.parsers.SAXParser;
importjavax.xml.parsers.SAXParserFactory;
importjava.io.InputStream;
publicclassMySax{
publicstaticvoidmain(String[] args)throwsException {
parseSku;
}
publicstaticvoidparseSkuthrowsException {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance;
SAXParser saxParser = saxParserFactory.newSAXParser;
InputStream inputStream = ClassLoader.getSystemResourceAsStream( "skus.xml");
saxParser.parse(inputStream, newSkuHandler);
}
}
输出结果如下:
{ "id": 345000, "name": "电脑A", "price": 5999.0}
{ "id": 345001, "name": "手机C", "price": 4599.0}
以上演示了 SAX 解析的基础原理 。EventModel 的 API 更复杂,同样通过重写 Event handler,实现 SAX 解析 。有兴趣的读者,请参见 POI 官网的示例代码: https://poi.apache.org/components/spreadsheet/how-to.html
EventModel 的局限
POI 官方提供的 EventModel API 虽然使用 SAX 方式解决了 DOM 解析的问题,但是存在一些局限性:
① 属于 low level API,抽象级别低,相对比较复杂,学习使用成本高 。
② 对于 HSSF 和 XSSF 类型的处理方式不同,代码需要根据不同类型分别做兼容 。
③ 未能完美解决内存溢出问题,内存开销仍有优化空间 。
④ 仅用于 Excel 解析,不支持 Excel 写入 。
因此,笔者 不建议使用 POI 原生的 EventModel,至于有哪些更推荐的工具,请看下文 。
推荐阅读
- 深入解析Redis的LRU与LFU算法实现
- 我们一起聊聊数据库与容器
- 逆水寒手游究竟好不好玩,逆水寒手游深度解析
- excel打印区域设置虚线怎样显示出来
- excel表格中如何换行打字内容
- excel表格里的斜线怎么画出来
- excel表格的斜划线怎么做的
- excel表格中画斜线表头
- excel表格画斜线写字
- excel 自动生成目录超链接
