Android之SAX、DOM和Pull解析XML
文章鏈接:http://blog.csdn.net/qq_16628781/article/details/70147230
知識點
- XML的3種解析方式:SAX、DOM和Pull;
- PULL解析XML文檔示例;
- Dom解析XML文檔示例;
- SAX解析XML文檔示例;
- 調用運行結果示例;
- 新名詞記錄{SAX、DOM和Pull解析XML文檔}
概述
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文檔和實體類
首先是XML文檔users.xml
<?xml version="1.0" encoding="UTF-8" ?>
<users>
<user id="1">
<userName>yaoj</userName>
<password>123456</password>
<age>24</age>
</user>
<user id="2">
<userName>tanksu</userName>
<password>3333333</password>
<age>23</age>
</user>
<user id="3">
<userName>fishing</userName>
<password>666666</password>
<age>26</age>
</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字段,因爲這個類是項目中其它用處,如果要解析,那麼直接在對應級別上面加入一個判斷就可以了。
PULL
在Android中,自帶的解析方法就是pull。它的優點在於便捷高效。下面我們直接看代碼:
/**
* 根據pull解析XML文檔
*
* @param inputStream 需要解析的輸入流
* @return 返回解析後的集合,可能爲空
*/
public static List<UserBean> parseXmlByPull(InputStream inputStream) {
XmlPullParser pullParser = Xml.newPullParser();
List<UserBean> 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: //開始解析文檔,最外層的節點<users>
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
DOM最初在HTML語言裏面認識到,因爲網頁也是使用可擴展語言XML來寫的,最初瀏覽器加載一個網頁的時候,就是將整個網頁都加入到內存裏面進行解析。這樣做的不好就是,可能我只是需要其中某些個數據,但是你都加載到內存了,這就造成了浪費。
廢話不多說,我們直接看如何利用DOM解析XML文檔。
/**
* 根據dom解析XML文檔
*
* @param inputStream 需要解析的輸入流
* @return 返回解析後的集合,可能爲空
*/
public static List<UserBean> parseUserByDom(InputStream inputStream) {
List<UserBean> userBeanList = new ArrayList<>();
try {
//生成一個文檔工廠類
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//
DocumentBuilder builder = factory.newDocumentBuilder();
//開始解析輸入流,因爲dom解析是首先將整個文檔讀入內存,所以這裏就完成了解析
//下面是對這個文檔取出操作
Document document = builder.parse(inputStream);
//獲取到文檔的根節點<users>
Element root = document.getDocumentElement();
//獲取到所有下一級節點<user>集合
//開始解析<user>節點及其子節點
NodeList nodeList = root.getElementsByTagName("user");
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
//獲取<user>的id屬性
int id = Integer.parseInt(element.getAttribute("id"));
UserBean userBean = new UserBean();
userBean.setId(id);
//獲取<user>節點下的子節點,並且遍歷判斷,取值
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));
}
}
}
//當一個<user>節點被解析完,那麼加入和集合中
userBeanList.add(userBean);
}
inputStream.close();
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
return userBeanList;
}
解釋:開始新建一個DocumentBuilder實體,進行解析輸入的XML文檔,然後獲取到根節點,再獲取到根節點下面所有的子節點,然後利用循環,對每一個< user>進行解析,包括其下面的各個子節點。如果子節點下面還有節點,那麼也需要element.getChildNodes()方法獲取到子節點的列表,在循環取值。如果還有更深的結構,同理可得。最後將獲取的數值設置給實體,加入到返回集合裏面去即可。
SAX
SAX方法會比較麻煩一點,因爲它需要你繼承DefaultHandler類,然後重寫相對應節點觸發的方法。和pull方法類似,不同的是API幫你分類好,到了那個節點,就會調用哪一個方法,而不用自己去判斷。
看下面的示例:
/**
* desc:SAX解析XML的處理類
* <p>
* author:kuyu.yaojt (tanksu)
* <p>
* email:[email protected]
* <p>
* blog:http://blog.csdn.net/qq_16628781
* <p>
* date:17/4/12
*/
public class SAXUtil extends DefaultHandler {
//解析的集合
private List<UserBean> userBeanList = new ArrayList<>();
//通過此變量,記錄當前標籤的名稱
private String curTagName;
UserBean userBean; //記錄當前Person
/**
* 提供給外部調用的方法
*
* @param inputStream 輸入流
* @return 解析的實體類集合
*/
public static List<UserBean> 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<UserBean> 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文檔。
解析XML文檔
我們這直接調用上面的幾個方法:
InputStream inputStream = getResources().openRawResource(R.raw.users);
List<UserBean> 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都可以。
以上就是所有內容,如有任何問題,請及時與我聯繫,謝謝。