StAX解析
除了DOM和SAX,還有一種解析XML文檔的模式StAX。StAX的全稱為The Stream API for XML。與SAX類似,StAX也是一邊解析,一邊處理,一邊釋放內存資源。
之所以將StAX與DOM、SAX分開來介紹,最重要的原因是,DOM和SAX都是推(PUSH)模式的解析模式,而StAX是拉(PULL)模式的解析模式(因此一般稱StAX解析為XML Pull解析)。是推模式還是拉模式,取決于在解析的過程中誰處于主導地位,是服務器還是客戶端。在推模式中,解析器自動解析XML文檔而不受用戶干預,但在拉模式中,用戶可以主動控制解析的進行,也就是主動控制事件的處理,主動調用相應的方法。理解了什么是推模式和拉模式后,StAX解析的思想也不難理解:XML文檔傳遞給解析器,通過next()
方法觸發(fā)文檔解析事件,用戶可以獲取當前事件 ,也可以調用相應的方法 :
與SAX不同的是,XML Pull中用int
型數(shù)據表示不同的事件:
如果想要查找龍族
這本書的價格,代碼為:
@Test
public void demo1() throws Exception {
XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();//構建解析器工廠
XmlPullParser xpp = xppFactory.newPullParser();//構建解析器
xpp.setInput(new FileInputStream("books.xml"), "utf-8");//將XML文檔和XML文檔的編碼方式傳遞給解析器
int event;
boolean isFound = false;//定義boolean變量用于標記
while ((event = xpp.getEventType()) != XmlPullParser.END_DOCUMENT) {//判斷當前事件是否為文檔結束事件
if (event == XmlPullParser.START_TAG && xpp.getName().equals("name")) {//找到name元素
String bookName = xpp.nextText();//獲取name元素的文本內容
if (bookName.equals("龍族")) {
isFound = true;//若這本書為《龍族》,將標記變量isFound置為true
}
if (event == XmlPullParser.START_TAG && xpp.getName().equals("price") && isFound == true) {//利用標記變量檢驗price元素是否為所要找的
System.out.println(xpp.nextText());//輸出《龍族》的價格
break;//跳出循環(huán)
}
xpp.next();// 觸發(fā)下一事件
}
}
用JUnit進行單體測試:
結果是正確的。
當然還有一種更簡單的方法,先通過name
元素找到龍族
這本書,再調用next()
方法找到price
元素,輸出價格。代碼實現(xiàn)相對容易,這里不再演示。
XML Pull解析還可以生成XML文檔,這個過程又稱為序列化。和單純地解析XML文檔相比,序列化的代碼有所不同:
XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();//構建解析器工廠
XmlSerializer xs = xppFactory.newSerializer();//構建序列化器
xs.setOutput(new FileOutputStream("books2.xml"), "utf-8");//將需要生成的XML文檔和XML文檔的編碼方式傳遞給序列化器
下面生成一個簡單的XML文檔:
@Test
public void demo2() throws Exception {
XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
XmlSerializer xs = xppFactory.newSerializer();
xs.setOutput(new FileOutputStream("personal_info.xml"), "utf-8");
xs.startDocument("utf-8", true);//設置XML文檔的編碼格式和standalone屬性
xs.startTag(null, "personal_info");//為personal_info元素設置元素開始事件
xs.startTag(null, "name");//為name元素設置元素開始事件
xs.text("超哥");//為name元素設置文本元素事件
xs.endTag(null, "name");//為name元素設置元素結束事件
xs.endTag(null, "personal_info");//為personal_info元素設置元素結束事件
xs.endDocument();//設置文檔結束事件
}
用JUnit進行單體測試,格式化personal_info.xml
文檔后查看:
操作成功。
在startTag()
和endTag()
方法中,第一個參數(shù)為名稱空間,一般為0
或null
,建議整個Java程序中統(tǒng)一為0
或null
。
在XML Pull解析中有一個非常重要的思想:這種解析模式決定了在解析的過程中是不能對數(shù)據進行修改的,但如果不立即釋放數(shù)據而是將其保存在內存中,那么就能對數(shù)據進行修改了。而對于這些數(shù)據,可以將它們封裝成List
集合對象:
對于文檔books.xml
,很顯然數(shù)據是根據書來存放的,那么就可以定一個book
類,類中定義name
、author
和price
三個屬性并生成Getters
和Setters
方法 :
public class Book{
private String name;
private String author;
private double price;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getAuthor(){
return author;
}
public void setAuthor(String author){
this.author = author;
}
public double getPrice(){
return price;
}
public void setPrice(double price){
this.price = price;
}
}
再將數(shù)據序列化生成XML文檔:
@Test
public void demo3() throws Exception{
Book book = new Book();
book.setName("天龍八部");
book.setAuthor("金庸");
book.setPrice(108.0);
XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
XmlSerializer xs = xppFactory.newSerializer();
xs.setOutput(new FileOutputStream("books3.xml"), "utf-8");
xs.startDocument("utf-8", true);
xs.startTag(null, "books");
xs.startTag(null, "book");
xs.startTag(null, "name");
xs.text(book.getName());
xs.endTag(null, "name");
xs.startTag(null, "author");
xs.text(book.getAuthor());
xs.endTag(null, "author");
xs.startTag(null, "price");
xs.text(String.valueOf(book.getPrice()));
xs.endTag(null, "price");
xs.endTag(null, "book");
xs.endTag(null, "books");
xs.endDocument();
}
用JUnit進行單體測試,格式化books3.xml
文檔后查看:
操作成功。
為了方便解析XML文檔和序列化生成XML文檔,可以定義一個工具類PullMethod
,類中定義這兩個方法(當然這個工具類只對book
對象生效,可以根據XML文檔的內容對類和方法進行修改):
public class PullMethod{
public static List<Book> parseXMLtoList(String fileName) throws Exception{//接收XML文檔,將文檔中的數(shù)據封裝成List集合對象
List<Book> books = new ArrayList<Book>();
Book book = null;
XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
XmlPullParser xpp = xppFactory.newPullParser();
xpp.setInput(new FileInputStream(fileName), "utf-8");
int event;
while ((event = xpp.getEventType()) != XmlPullParser.END_DOCUMENT){
if (event == XmlPullParser.START_TAG && xpp.getName().equals("book")){
book = new Book();定義Book類對象book
}
if (event == XmlPullParser.START_TAG && xpp.getName().equals("name")){
book.setName(xpp.nextText());//為book設置name屬性
}
if (event == XmlPullParser.START_TAG && xpp.getName().equals("author")){
book.setAuthor(xpp.nextText());//為book設置author屬性
}
if (event == XmlPullParser.START_TAG && xpp.getName().equals("price")){
book.setPrice(Double.parseDouble(xpp.nextText()));//為book設置price屬性
}
if (event == XmlPullParser.END_TAG && xpp.getName().equals("book")){
books.add(book);//將book添加至集合中
}
xpp.next();
}
return books;//返回List集合對象
}
public static void serializeListtoXML(List<Book> books, String fileName) throws Exception{//接收List集合對象,將集合對象序列化成XML文檔
XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
XmlSerializer xs = xppFactory.newSerializer();
xs.setOutput(new FileOutputStream(fileName), "utf-8");
xs.startDocument("utf-8", true);
xs.startTag(null, "books");
for (Book book : books){//foreach語句遍歷集合
xs.startTag(null, "book");
xs.startTag(null, "name");
xs.text(book.getName());
xs.endTag(null, "name");
xs.startTag(null, "author");
xs.text(book.getAuthor());
xs.endTag(null, "author");
xs.startTag(null, "price");
xs.text(String.valueOf(book.getPrice()));
xs.endTag(null, "price");
xs.endTag(null, "book");
}
xs.endTag(null, "books");
xs.endDocument();
}
}
可以這樣測試這兩個方法是否正確:
@Test
public void demo4() throws Exception{
List<Book> books = PullMethod.parseXMLtoList("books.xml");
PullMethod.serializeListtoXML(books, "books3.xml");
}
原理很簡單,就是將books.xml
文檔先解析再生成,如果序列化后的文檔和原文檔完全一樣,則證明工具類中的兩個方法是正確的:
有了工具類的兩個方法,就能夠很方便地對XML文檔中的數(shù)據進行修改了:
@Test
public void demo5() throws Exception{//增加數(shù)據
List<Book> books = PullMethod.parseXMLtoList("books.xml");
Book book = new Book();
book.setName("三國演義");
book.setAuthor("羅貫中");
book.setPrice(39.6);
books.add(book);
PullMethod.serializeListtoXML(books, "books3.xml");
}
@Test
public void demo6() throws Exception{//修改數(shù)據
List<Book> books = PullMethod.parseXMLtoList("books.xml");
for(Book book:books){
if(book.getName().equals("三體")){
book.setPrice(book.getPrice() * 0.5);
}
}
PullMethod.serializeListtoXML(books, "books3.xml");
}
@Test
public void demo7() throws Exception{//刪除數(shù)據
List<Book> books = PullMethod.parseXMLtoList("books.xml");
for(Book book:books){
if(book.getName().equals("三體")){
books.remove(book);//從集合中刪除book
break;
}
}
PullMethod.serializeListtoXML(books, "books3.xml");
}
學習提示
這就是XML的三種解析模式,無論是DOM、SAX還是StAX,實際上都不難掌握,關鍵是要記住各自的思想和解析時調用的方法。在學習的過程中一定要善于查閱API文檔,這樣才能更好地理解與運用它們。