前段时间,在写一段解析xml的代码时发现了一个问题。我用的是SAX,这确实是很简单好用的一个东东。我们只需要继承DefaulHandler ,实现其中的方法即可。但是我们要注意到,其中的 characters 方法在解析一个节点的时候是可能会执行多次的。
假设我们的 persons.xml 文件如下:
<?xml version="1.0" encoding="utf-8"?> <persons> <person> <name>Tom Jeff</name> <sex>M</sex> <age>20</age> </person> <person> <name>Cater</name> <sex>M</sex> <age>23</age> </person> <person> <name>Susan</name> <sex>F</sex> <age>19</age> </person> <person> <name>Lily</name> <sex>F</sex> <age>22</age> </person> </persons>
/** * @Title: MySaxHandler.java * @Description: TODO * @author ThinkPad * @version 1.0 * @date 2014年7月13日 */ package com.sax.example; import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * @author ThinkPad * */ public class MySaxHandler1 extends DefaultHandler { /** * xml 解析结果 */ public static List<Person> personList; private Person person; private String node; private boolean flag = false; public void startDocument () throws SAXException { //开始解析文档 super.startDocument(); personList = new ArrayList<Person>(); } public void endDocument () throws SAXException { //文档解析结束 super.endDocument(); } public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { //开始解析节点 super.startElement(uri, localName, qName, attributes); flag = true; if( qName.equals("person")){ person = new Person(); } node = qName; } public void characters (char[] ch, int start, int length) throws SAXException { //保存节点内容 super.characters(ch, start, length); if(!flag) { return; } String s = new String(ch, start, length); switch (node) { case "name": person.setName(s); break; case "sex": person.setSex(s); break; case "age": person.setAge(s); break; default: break; } } public void endElement (String uri, String localName, String qName) throws SAXException { //结束解析节点 super.endElement(uri, localName, qName); flag = false; if( qName.equals("person")){ personList.add(person); } } }
当然,就解析上面的那段xml ,这是没有问题的。然而让人难堪的是,我想改一下xml ,在某些节点加上 \r \n \t 之类的字符,来证实这样解析会丢失部分数据,却没有成功。它一直工作的很好??? 很多人在博文里说“,当遇到内容中有回车,\t等等内容时,characters 方法有可能会执行多次”,看来在这种情况下也未必一定会执行多次。这是随机的? 总之,我在生产环境中,使用类似上面的解析方法确实遇到了截断节点数据的问题。因此,我确信,上面的写法是有问题的。
正确的、合理的写法如下,用一个StringBuilder 在characters 方法中拼接数据,在 endElement 方法中填充数据。
/** * @Title: MySaxHandler.java * @Description: TODO * @author ThinkPad * @version 1.0 * @date 2014年7月13日 */ package com.sax.example; import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * @author ThinkPad * */ public class MySaxHandler extends DefaultHandler { /** * xml 解析结果 */ public static List<Person> personList; private Person person; private String node; private StringBuilder sb; private boolean flag = false; public void startDocument () throws SAXException { //开始解析文档 super.startDocument(); personList = new ArrayList<Person>(); } public void endDocument () throws SAXException { //文档解析结束 super.endDocument(); } public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { //开始解析节点 super.startElement(uri, localName, qName, attributes); flag = true; if( qName.equals("person")){ person = new Person(); } node = qName; sb = new StringBuilder(); } public void characters (char[] ch, int start, int length) throws SAXException { //保存节点内容 super.characters(ch, start, length); if(!flag) { return; } sb.append(new String(ch, start, length) ); } public void endElement (String uri, String localName, String qName) throws SAXException { //结束解析节点 super.endElement(uri, localName, qName); flag = false; if( qName.equals("person")){ personList.add(person); } String s = sb.toString(); switch (node) { case "name": person.setName(s); break; case "sex": person.setSex(s); break; case "age": person.setAge(s); break; default: break; } } }