XML的解析有3中解析方式:SAX、DOM、Pull。
SAX(Simple API for XML) 使用流式处理的方式,它并不记录所读内容的相关信息。它是一种以事件为驱动的XML API,解析速度快,占用内存少。使用回调函数来实现。 缺点是不能倒退。
DOM(Document Object Model) 是一种用于XML文档的对象模型,可用于直接访问XML文档的各个部分。它是一次性全部将内容加载在内存中,生成一个树状结构,它没有涉及回调和复杂的状态管理。 缺点是加载大文档时效率低下。
Pull内置于Android系统中。也是官方解析布局文件所使用的方式。Pull与SAX有点类似,都提供了类似的事件,如开始元素和结束元素。不同的是,SAX的事件驱动是回调相应方法,需要提供回调的方法,而后在SAX内部自动调用相应的方法。而Pull解析器并没有强制要求提供触发的方法。因为他触发的事件不是一个方法,而是一个数字。它使用方便,效率高。
SAX、DOM、Pull的比较:
1. 内存占用:SAX、Pull比DOM要好;
2. 编程方式:SAX采用事件驱动,在相应事件触发的时候,会调用用户编好的方法,也即每解析一类XML,就要编写一个新的适合该类XML的处理类。DOM是W3C的规范,Pull简洁。
3. 访问与修改:SAX采用流式解析,DOM随机访问。
4. 访问方式:SAX,Pull解析的方式是同步的,DOM逐字逐句。
下面是每个解析方式的使用方法讲解示例。
首先是XML文档users.xml
<users>
<user id="1">
<userName>yaojuserName>
<password>123456password>
<age>24age>
user>
<user id="2">
<userName>tanksuuserName>
<password>3333333password>
<age>23age>
user>
<user id="3">
<userName>fishinguserName>
<password>666666password>
<age>26age>
user>
users>
因为要解析成对象,那么就必须要有对应的实体类。
public class UserBean implements Serializable {
//串行化版本统一标识符
private static final long serialVersionUID = 1L;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
private int id;
private String userName;
private transient String password;
private int age;
public static class InstanceHolder {
private static final UserBean userBean = new UserBean("tanksu", "999999", 12);
}
private Object readResolve() throws ObjectStreamException {
return InstanceHolder.userBean;
}
public UserBean() {
}
public UserBean(String userName, String password, int age) {
this.userName = userName;
this.password = password;
this.age = age;
}
//初始化version版本号
private final long version = 2L;
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
if (version == 1L) {
out.writeObject(password);
} else if (version == 2L) {
out.writeObject(password);
} else {
out.writeObject(password);
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
password = (String) in.readObject();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "{" +
"id:" + id +
", userName:" + userName +
", password:" + password +
", age:" + age +
", version:" + version +
"}";
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
上面的实体类请忽略version字段,因为这个类是项目中其它用处,如果要解析,那么直接在对应级别上面加入一个判断就可以了。
在Android中,自带的解析方法就是pull。它的优点在于便捷高效。下面我们直接看代码:
/**
* 根据pull解析XML文档
*
* @param inputStream 需要解析的输入流
* @return 返回解析后的集合,可能为空
*/
public static List parseXmlByPull(InputStream inputStream) {
XmlPullParser pullParser = Xml.newPullParser();
List userBeanList = null;
try {
pullParser.setInput(inputStream, "UTF-8");
//START_TAG, END_TAG, TEXT等等的节点
int eventType = pullParser.getEventType();
UserBean userBean = null;
//当还没有解析到结束文档的节点,一直循环
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT: //开始解析文档,最外层的节点
userBeanList = new ArrayList<>();
break;
case XmlPullParser.START_TAG: //开始解析到节点,需要对每一个定义的节点进行处理
if ("user".equals(pullParser.getName())) {
userBean = new UserBean();
int id = Integer.parseInt(pullParser.getAttributeValue(0));
userBean.setId(id);
} else if ("userName".equals(pullParser.getName())) {
if (userBean != null) userBean.setUserName(pullParser.nextText());
} else if ("passwor".equals(pullParser.getName())) {
if (null != userBean) userBean.setPassword(pullParser.nextText());
} else if ("age".equals(pullParser.getName())) {
if (null != userBean)
userBean.setAge(Integer.parseInt(pullParser.nextText()));
}
break;
case XmlPullParser.END_TAG: //结束节点,将解析的userbean加入到集合中
if ("user".equals(pullParser.getName())) {
userBeanList.add(userBean);
userBean = null;
}
break;
case XmlPullParser.END_DOCUMENT:
break;
}
//获取到下一个节点,在触发解析动作
eventType = pullParser.next();
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
return userBeanList;
}
解释:首先取得XmlPullParser类的一个实例,然后将输入流进行解析,解析的结果会放到pullParser里面,获取到eventType,这里的eventType对应的分别有文档开始(START_DOCUMENT)、文档结束(END _DOCUMENT)、节点开始(START _TAG)、节点结束(END _TAG)等;然后根据每个不同的节点或者文档结束,取出不同的数值,设置给
userBean,最后加入到返回的集合里面去。
DOM最初在HTML语言里面认识到,因为网页也是使用可扩展语言XML来写的,最初浏览器加载一个网页的时候,就是将整个网页都加入到内存里面进行解析。这样做的不好就是,可能我只是需要其中某些个数据,但是你都加载到内存了,这就造成了浪费。
废话不多说,我们直接看如何利用DOM解析XML文档。
/**
* 根据dom解析XML文档
*
* @param inputStream 需要解析的输入流
* @return 返回解析后的集合,可能为空
*/
public static List parseUserByDom(InputStream inputStream) {
List userBeanList = new ArrayList<>();
try {
//生成一个文档工厂类
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//
DocumentBuilder builder = factory.newDocumentBuilder();
//开始解析输入流,因为dom解析是首先将整个文档读入内存,所以这里就完成了解析
//下面是对这个文档取出操作
Document document = builder.parse(inputStream);
//获取到文档的根节点
Element root = document.getDocumentElement();
//获取到所有下一级节点集合
//开始解析节点及其子节点
NodeList nodeList = root.getElementsByTagName("user");
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
//获取的id属性
int id = Integer.parseInt(element.getAttribute("id"));
UserBean userBean = new UserBean();
userBean.setId(id);
//获取节点下的子节点,并且遍历判断,取值
NodeList childNodes = element.getChildNodes();
for (int y = 0; y < childNodes.getLength(); y++) {
if (childNodes.item(y).getNodeType() == Node.ELEMENT_NODE) {
if ("userName".equals(childNodes.item(y).getNodeName())) {
String name = childNodes.item(y).getFirstChild().getNodeValue();
userBean.setUserName(name);
} else if ("password".equals(childNodes.item(y).getNodeName())) {
String password = childNodes.item(y).getFirstChild().getNodeValue();
userBean.setPassword(password);
} else if ("age".equals(childNodes.item(y).getNodeName())) {
String age = childNodes.item(y).getFirstChild()
.getNodeValue();
userBean.setAge(Integer.parseInt(age));
}
}
}
//当一个节点被解析完,那么加入和集合中
userBeanList.add(userBean);
}
inputStream.close();
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
return userBeanList;
}
解释:开始新建一个DocumentBuilder实体,进行解析输入的XML文档,然后获取到根节点,再获取到根节点下面所有的子节点,然后利用循环,对每一个< user>进行解析,包括其下面的各个子节点。如果子节点下面还有节点,那么也需要element.getChildNodes()方法获取到子节点的列表,在循环取值。如果还有更深的结构,同理可得。最后将获取的数值设置给实体,加入到返回集合里面去即可。
SAX方法会比较麻烦一点,因为它需要你继承DefaultHandler类,然后重写相对应节点触发的方法。和pull方法类似,不同的是API帮你分类好,到了那个节点,就会调用哪一个方法,而不用自己去判断。
看下面的示例:
/**
* desc:SAX解析XML的处理类
*
* author:kuyu.yaojt (tanksu)
*
* email:[email protected]
*
* blog:http://blog.csdn.net/qq_16628781
*
* date:17/4/12
*/
public class SAXUtil extends DefaultHandler {
//解析的集合
private List userBeanList = new ArrayList<>();
//通过此变量,记录当前标签的名称
private String curTagName;
UserBean userBean; //记录当前Person
/**
* 提供给外部调用的方法
*
* @param inputStream 输入流
* @return 解析的实体类集合
*/
public static List parse(InputStream inputStream) throws Exception {
//创建SAXParserFactory解析工厂类
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXUtil saxUtil = new SAXUtil();
//实例化一个SAXParser解析类
SAXParser saxParser = factory.newSAXParser();
//开始解析文档
//参数2:利用我们定义的handler进行解析输入的文档
saxParser.parse(inputStream, saxUtil);
return saxUtil.getUserBeanList();
}
private SAXUtil() {
}
private List getUserBeanList() {
return userBeanList;
}
//开始解析文档,做初始化工作
@Override
public void startDocument() throws SAXException {
userBeanList = new ArrayList<>();
super.startDocument();
}
//开始解析元素
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
//uri:命名空间的uri,如果没有,则为""
//localName标签名
// fullName带命名空间的标签名
// attribute存放该标签所有属性
//attributes:该元素所对应的的所有属性
if ("user".equals(localName)) {
for (int i = 0; i < attributes.getLength(); i++) {
userBean = new UserBean();
//根据index拿到属性的name,因为可能不止一个属性
String localAttributeName = attributes.getLocalName(i);
if ("id".equals(localAttributeName)) {
//根据属性名拿到值
String id = attributes.getValue(uri, localAttributeName);
userBean.setId(Integer.valueOf(id));
}
}
}
curTagName = localName;
}
//解析字符
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
//根据char数组生产一个字符串
String sData = new String(ch, start, length).trim();
if ("userName".equals(curTagName)) {
userBean.setUserName(sData);
} else if ("password".equals(curTagName)) {
userBean.setPassword(sData);
} else if ("age".equals(curTagName)) {
userBean.setAge(Integer.parseInt(sData));
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if ("user".equals(localName)) {
//当一个用户被解析完,加入到集合中
userBeanList.add(userBean);
userBean = null;
}
curTagName = null;
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
解释:这里的逻辑可以参照pull解析,不多讲。主要提一下,因为没有类中没有返回当前节点名称的方法,所以需要自己记录。上面的类,我私有化了构造方法,并且提供了一个静态的方法来使用sax解析XML文档。
我们这直接调用上面的几个方法:
InputStream inputStream = getResources().openRawResource(R.raw.users);
List userBeanList = ParseUtil.parseXmlByPull(inputStream);
userBeanList = ParseUtil.parseUserByDom(inputStream);
try {
userBeanList = SAXUtil.parse(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
for (UserBean userBean : userBeanList){
CommonLog.logInfo("-=-=-=>>", userBean.toString());
}
因为都是解析同一个XML文档,解析出来的结果都是一样的。所以下面只给出一个结果图。
总的来说,3中解析XML的方法各有优劣。使用方法大同小异,只要理解了XML文档的结构,使用哪一种方法都不是问题了。但是不推荐使用DOM,因为他是全部读入内存来解读,而且解析的代码感觉不是很清晰。推荐使用pull或者sax都可以。
以上就是所有内容,如有任何问题,请及时与我联系,谢谢。