@liayun
2016-06-29T15:48:08.000000Z
字数 15200
阅读 2118
JavaWeb
XML解析方式分为两种:dom和sax。
dom解析和sax解析的区别(面试题常考)
dom解析原理:
root.xml文档如下:
<?xml version="1.0"?>
<root>
<head>
<title>example</title>
</head>
<body>
<p>content1</p>
<p>content2</p>
</body>
<foot>
<author name="liayun" />
</foot>
</root>
对其进行dom解析,如图:
如何调整JVM内存大小?(面试题也常考)
JVM最多允许你占用64M的内存,你所写的程序控制的内存超出64M,如果不调整JVM内存大小,根本就运行不起来。
例,
byte[] b = new byte[1024*1024*1024];
这句代码一执行,JVM就在内存里面开辟1G的空间,这时就会包内存溢出异常:java.lang.OutOfMemoryError: Java heap space
。
调整JVM内存大小的步骤如图:
sax解析原理:
对以上文档进行sax解析,如图:
XML解析器
使用哪种解析器对程序员基本上没有什么影响,我们学习的是解析开发包,解析开发包调用什么样的解析器对程序员没有意义。
JAXP:Java API for xml Processing,jaxp是sun提供的一套xml解析API,jaxp很好地支持了dom和sax解析方式。
JAXP开发包是J2SE的一部分,它由javax.xml、org.w3c.dom 、org.xml.sax包及其子包组成。
在javax.xml.parsers包中,定义了几个工厂类,程序员调用这些工厂类,可以得到对xml文档进行解析的DOM或SAX的解析器对象。
javax.xml.parsers包中的DocumentBuilderFactory用于创建DOM模式的解析器对象,DocumentBuilderFactory是一个抽象工厂类,它不能直接实例化,但该类提供了一个newInstance方法,这个方法会根据本地平台默认安装的解析器,自动创建一个工厂的对象并返回。
调用DocumentBuilderFactory.newInstance()方法得到创建DOM解析器的工厂。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
调用工厂对象的newDocumentBuilder方法得到DOM解析器对象。
DocumentBuilder builder = factory.newDocumentBuilder();
调用DOM解析器对象的parse()方法解析XML文档,得到代表整个文档的Document对象,进而可以利用DOM特性对整个XML文档进行操作了。
Document document = builder.parse("src/book.xml");
DOM模型(document object model)
DOM解析器在解析XML文档时,会把文档中的所有元素,按照其出现的层次关系,解析成一个个Node对象(节点),在dom中,节点之间关系如下:
节点类型:
类型名 | 说明 |
---|---|
Node | 表示所有类型值的统一接口,此接口中提供了很多增删改查节点的方法,所有的文档树中的对象都实现过此接口 |
Document | 表示文档类型 |
Element | 表示元素节点类型 |
Text | 表示文本节点类型 |
CDATASection | 表示CDATA区域类型 |
DocumentType | 表示文档声明类型 |
DocumentFragment | 表示文档片段类型 |
Attr | 表示属性节点类型 |
Node对象
注意:dom解析下,xml文档的每一个组成部分都会用一个对象表示,例如标签用Element,属性用Attr,但不管什么对象,都是Node的子类,所以在开发中可以把获取到的任意节点都当作Node对待。
例如,有book.xml文档如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<书架>
<书>
<书名 name="xxxxx">Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>39.00元</售价>
</书>
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>28.00元</售价>
</书>
</书架>
使用dom方式对xml文档进行crud
读取book.xml文档中书名元素(标签)<书名>JavaScript网页开发</书名>
中封装的内容。
public void read1() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
NodeList list = document.getElementsByTagName("书名"); // 获取到书名元素(标签)
Node node = list.item(1);
String content = node.getTextContent(); // 获取到书名元素(标签)中封装的内容
System.out.println(content); // 输出:JavaScript网页开发
}
得到xml文档中的所有标签。须使用递归,可断点调试跟踪,理解其原理。
NodeList getChildNodes()
:包含此节点的所有子节点的NodeList。如果不存在子节点,则这是不包含节点的NodeList。
public void read2() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
// 得到根节点
Node root = document.getElementsByTagName("书架").item(0);
list(root);
}
private void list(Node node) {
if(node instanceof Element)
System.out.println(node.getNodeName());
NodeList list = node.getChildNodes();
for(int i = 0; i < list.getLength(); i++) {
Node child = list.item(i);
list(child);
}
}
千万注意:对于XML标签中出现的所有空格和换行,XML解析程序都会当作标签内容进行处理。
得到xml文档中标签<书名 name="xxxx">Java就业培训教程</书名>
属性的值。
public void read3() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
Element bookName = (Element) document.getElementsByTagName("书名").item(0);
String value = bookName.getAttribute("name");
System.out.println(value);
}
向xml文档中添加节点:<售价>59.00元</售价>
。
获得JAXP中的DOM解析器,调用DOM解析器对象的parse()方法解析XML文档,得到代表整个文档的Document对象。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
创建节点。
Element price = document.createElement("售价");
price.setTextContent("59.00元");
把创建的节点挂到第一本书上。
Element book = (Element) document.getElementsByTagName("书").item(0);
book.appendChild(price);
更新XML文档,把更新后的内存树写回到xml文档。
javax.xml.transform包中的Transformer类用于把代表XML文件的Document对象转换为某种格式后进行输出,例如把xml文件应用样式表后转成一个html文档。利用这个对象,当然也可以把Document对象又重新写入到一个XML文件中。Transformer对象通过TransformerFactory获得。
Transformer类通过transform方法完成转换操作,该方法接收一个源和一个目的地,我们可以通过:
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tf = tfFactory.newTransformer();
tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream("src/book.xml")));
添加后book.xml内容:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架>
<书>
<书名 name="xxxxx">Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>39.00元</售价>
<售价>59.00元</售价></书>
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>28.00元</售价>
</书>
</书架>
向xml文档中指定位置上添加节点:<售价>59.00元</售价>
获得JAXP中的DOM解析器,调用DOM解析器对象的parse()方法解析XML文档,得到代表整个文档的Document对象。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
创建节点。
Element price = document.createElement("售价");
price.setTextContent("59.00元");
得到参考节点。
Element refNode = (Element) document.getElementsByTagName("售价").item(0);
得到要挂崽的节点。
Element book = (Element) document.getElementsByTagName("书").item(0);
往book节点的指定位置插崽。
book.insertBefore(price, refNode);
把更新后的内存树写回到xml文档。
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tf = tfFactory.newTransformer();
tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream("src/book.xml")));
添加后book.xml内容:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架>
<书>
<书名 name="xxxxx">Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>59.00元</售价><售价>39.00元</售价>
</书>
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>28.00元</售价>
</书>
</书架>
向xml文档节点上添加属性:<书名>Java就业培训教程</书名>
上添加name="xxxxx"属性。为了进行以下代码的测试,先把原book.xml文档中<书名>Java就业培训教程</书名>
元素中的属性name删除掉。
public void addAttr() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
Element bookName = (Element) document.getElementsByTagName("书名").item(0);
bookName.setAttribute("name", "xxxxx");
// 把更新后的内存树写回到xml文档
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tf = tfFactory.newTransformer();
tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream("src/book.xml")));
}
添加name="xxxxx"属性之后的book.xml内容:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架>
<书>
<书名 name="xxxxx">Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>39.00元</售价>
</书>
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>28.00元</售价>
</书>
</书架>
注意:以上方式添加的元素都没有格式上的缩进。
例,book.xml文档修改为如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<书架>
<书>
<书名 name="xxxxx">Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>59.00元</售价>
<售价>39.00元</售价>
</书>
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>28.00元</售价>
</书>
</书架>
删除xml文档中的标签<售价>59.00元</售价>
。
获得JAXP中的DOM解析器,调用DOM解析器对象的parse()方法解析XML文档,得到代表整个文档的Document对象。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
得到要删除的节点。
Element e = (Element) document.getElementsByTagName("售价").item(0);
删除节点有两种方式:
第一种方式:
得到要删除的节点的爸爸。
Element book = (Element) document.getElementsByTagName("书").item(0);
爸爸再删崽。
book.removeChild(e);
第二种方式:
得到要删除的节点的爸爸之后再删崽。
e.getParentNode().removeChild(e);
在做开发时,有可能要删除节点所在的节点。
e.getParentNode().getParentNode().removeChild(e.getParentNode());
删除整个xml文档(整个xml文档的爸爸是document)。
e.getParentNode().getParentNode().getParentNode().removeChild(e.getParentNode().getParentNode());
把更新后的内存树写回到xml文档。
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tf = tfFactory.newTransformer();
tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream("src/book.xml")));
删除节点之后book.xml内容为:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架>
<书>
<书名 name="xxxxx">Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>39.00元</售价>
</书>
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>28.00元</售价>
</书>
</书架>
更新节点<售价>59.00元</售价>
中的售价。
public void update() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse("src/book.xml");
Element e = (Element) document.getElementsByTagName("售价").item(0);
e.setTextContent("109元");
// 把更新后的内存树写回到xml文档
TransformerFactory tfFactory = TransformerFactory.newInstance();
Transformer tf = tfFactory.newTransformer();
tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream("src/book.xml")));
}
修改之后book.xml文档的内容为:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架>
<书>
<书名 name="xxxxx">Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>109元</售价>
<售价>39.00元</售价>
</书>
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>28.00元</售价>
</书>
</书架>
SAX采用事件处理的方式解析XML文件,利用SAX解析XML文档,涉及两个部分:解析器和事件处理器。
阅读ContentHandler API文档,常用方法有:startElement、endElement、characters。
例,有book.xml文档如下:
<?xml version="1.0" encoding="UTF-8"?>
<书架>
<书>
<书名>Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>109元</售价>
</书>
<书>
<书名 name="xxxx">JavaScript网页开发</书名>
<作者>黎活明</作者>
<售价>28.00元</售价>
</书>
</书架>
SAX方式解析XML文档的步骤:
使用SAXParserFactory创建SAX解析工厂。
SAXParserFactory factory = SAXParserFactory.newInstance();
通过SAX解析工厂得到解析器对象。
SAXParser sp = factory.newSAXParser();
通过解析器对象得到一个XML的读取器。
XMLReader reader = sp.getXMLReader();
设置读取器的事件处理器,一般是内容处理器,即ContentHandler。
XMLReader reader = sp.getXMLReader();
解析xml文件。
reader.parse("src/book.xml");
ContentHandler是一个接口,我们可以自己写一个类实现这个接口,其中提供了如下重要的方法:
void characters(char[] ch, int start, int length)
:接收字符数据的通知。endDocument()
:接收文档的结尾的通知。startDocument()
:接收文档的开始的通知。startElement(String uri, String localName, String qName, Attributes atts)
:接收元素开始的通知。void endElement(String uri, String localName, String qName)
:接收元素结束的通知。
编写一个类ListHandler实现ContentHandler接口,其作用是得到xml文档所有内容。
class ListHandler implements ContentHandler {
@Override
public void setDocumentLocator(Locator locator) {
// TODO Auto-generated method stub
}
@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
}
@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
// TODO Auto-generated method stub
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
// TODO Auto-generated method stub
}
/*
* 解析器只要解析到开始标签,就会调用startElement()方法
* 会通过方法的参数告诉你解析到的当前标签名称是什么
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
System.out.println("<"+qName+">");
for(int i = 0; atts != null && i < atts.getLength(); i++) { // 有可能抛空指针异常,代码写的越紧凑越好
String attName = atts.getQName(i);
String attValue = atts.getValue(i);
System.out.println(attName+"="+attValue);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("<"+qName+">");
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
System.out.println(new String(ch, start, length));
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
// TODO Auto-generated method stub
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
// TODO Auto-generated method stub
}
@Override
public void skippedEntity(String name) throws SAXException {
// TODO Auto-generated method stub
}
}
可发现ContentHandler接口中的方法都要一一实现,未免麻烦。注意DefaultHandler类,该类实现了某些处理xml文档必须的接口——ContentHandler,但是均没有具体的方法,也就是说是空方法,如果想要解析xml文档,需要覆写该方法。
编写一个类TagValueHandler实现DefaultHandler类,其作用是获取指定标签的值:<作者>张孝祥</作者>
。
class TagValueHandler extends DefaultHandler {
private String currentTag; // 记住当前解析到的是什么标签
private int needNumber = 1; // 记住想获取第几个作者标签的值
private int currentNumber; // 当前解析到的是第几个
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
currentTag = qName;
if(currentTag.equals("作者")) {
currentNumber++;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
currentTag = null;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if("作者".equals(currentTag) && currentNumber == needNumber) {
System.out.println(new String(ch, start, length));
}
}
}
在实际开发中,我们是要把xml文档中的每一本书封装到一个book对象中,并把多个book对象放在一个List集合中返回。
先将封装数据的JavaBean,即Book.java设计出来:
public class Book {
private String name;
private String author;
private String 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 String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
再编写一个类BeanListHandler实现DefaultHandler类,把xml文档中的每一本书封装到一个book对象中,并把多个book对象放在一个List集合中返回。
class BeanListHandler extends DefaultHandler {
private List list = new ArrayList();
private String currentTag;
private Book book;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
currentTag = qName;
if("书".equals(currentTag)) {
book = new Book();
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if("书名".equals(currentTag)) { // currentTag放在前面同样有问题
String name = new String(ch, start, length);
book.setName(name);
}
if("作者".equals(currentTag)) {
String author = new String(ch, start, length);
book.setAuthor(author);
}
if("售价".equals(currentTag)) {
String price = new String(ch, start, length);
book.setPrice(price);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(qName.equals("书")) {
list.add(book);
book = null;
}
currentTag = null; //不置空不行
}
public List getBooks() {
return list;
}
}
BeanListHandler类中要注意两点:
endElement(String uri, String localName, String qName)
方法中currentTag变量一定要置为空,不置为空绝对不行,会报异常java.lang.NullPointerException,若要知道其原因,可断点跟踪sax解析案例,分析程序问题,可发现其原因就是对于XML标签中出现的所有空格和换行,XML解析程序都会当作标签内容进行处理。characters(char[] ch, int start, int length)
方法中,currentTag依次和标签名判断时,一定要放在后面,currentTag放在前面同样有问题。最后,在main方法中测试:
public class Demo {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// 1.创建解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
// 2.得到解析器
SAXParser sp = factory.newSAXParser();
// 3.得到读取器
XMLReader reader = sp.getXMLReader();
// 4.设置内容处理器
BeanListHandler handler = new BeanListHandler();
reader.setContentHandler(handler);
// 5.读取xml文档内容
reader.parse("src/book.xml");
@SuppressWarnings("unchecked")
List<Book> list = handler.getBooks();
System.out.println(list);
}
class BeanListHandler extends DefaultHandler {
...
}
}
这里我参考: