【Java基础】关于XML结构数据的使用

一.什么是XML?

XML是可扩展标记语言(eXtensible Markup Language)的缩写,它是是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据

例如,一个表示书籍的XML对象可能如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>

二.XML特点

  1. 纯文本默认使用UTF-8编码
  2. 可嵌套,适合表示结构化数据。如果把XML内容存为文件,那么它就是一个XML文件,例如:book.xml
  3. XML经常通过网络作为消息传输

三.XML结构

1.基本结构

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>

XML固定的结构

  1. 首行必定是<?xml version="1.0" encoding="UTF-8" ?>,声明文档的版本号以及编码格式

  2. 第二行声明的是<!DOCTYPE note SYSTEM "book.dtd"> ,声明是对外部 DTD 文件的引用,定义当前文档结构

    DTD 的目的是定义 XML 文档的结构。它使用一系列合法的元素来定义文档结构:

    <!DOCTYPE book
    [
    <!ELEMENT book(name,author,isbn tags,pubDate)>
    <!ELEMENT name(#PCDATA)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT isbn (#PCDATA)>
    <!ELEMENT tags(#PCDATA)>
    <!ELEMENT pubDate(#PCDATA)>
    ]>
    
  3. XML的文档内容

    • 一个XML文档有且仅有一个根元素
    • 根元素可以包含任意个子元素元素可以包含属性,例如: <isbn lang="CN">1234567</isbn> <isbn>包含一个属性lang="CN"
    • 元素必须正确嵌套。
    • 如果是空元素,可以用<tag/>表示。
  4. 所有的 XML 元素都必须有一个关闭标签,在 XML 中,省略关闭标签是非法的。

  5. XML 标签对大小写敏感

  6. XML 属性值必须加引号

2.特殊字符

由于使用了<、>以及引号等标识符为xml文档关键字,如果内容出现了改符号, 必须进行转义

例如,Java<tm>必须写成

<name>Java&lt;tm&gt;</name>

常见的特殊字符如下:

字符 表示
< &lt;
> &gt;
& &amp;
" &quot;
' &apos;

3.验证XML文件的正确性

  • 格式正确的XML(Well Formed)是指 : XML的格式是正确的,可以被解析器正常读取
  • 合法的XML是指: 不但XML格式正确,而且它的数据结构可以被DTD或者XSD验证

DTD文档可以指定一系列规则,例如:

  • 根元素必须是book
  • book元素必须包含name,author等指定元素
  • isbn元素必须包含属性lang

如何验证XML文件的正确性呢?

  • 最简单的方式是通过浏览器验证。 可以直接把XML文件拖拽到浏览器窗口如果格式错误,浏览器会报错。
  • 和结构类似的HTML不同,浏览器对HTML有一定的“容错性”缺少结束标签也可以被解析,但XML要求严格的格式,任何没有正确嵌套的标签都会导致错误。

XML是一个技术体系,除了我们经常用到的XML文档本身外,XML还支持:

  • DTD和XSD:验证XML结构和数据是否有效;
  • Namespace:XML节点和属性的名字空间;
  • XSLT:把XML转化为另一种文本;
  • XPath:一种XML节点查询语言;

实际上,XML的这些相关技术实现起来非常复杂,在实际应用中很少用到,通常了解一下就可以了。

4.XML标签命名规则

  • 名称可以包含字母、数字以及其他的字符
  • 名称不能以数字或者标点符号开始
  • 名称不能以字母 xml(或者 XML、Xml 等等)开始
  • 名称不能包含空格

最佳命名习惯

  • 推荐使用下划线进行命名:<first_name>、<last_name>。

  • 名称应简短和简单,比如:<book_title>,而不是:<the_title_of_the_book>。

  • 避免 “-” 字符。如:“first-name”,一些软件会认为您想要从 first 里边减去 name。

  • 避免 “.” 字符。如:“first.name”,一些软件会认为 “name” 是对象 “first” 的属性。

  • 避免 “:” 字符。冒号会被转换为命名空间来使用

5.XML小结

  • XML使用嵌套结构的数据表示方式,支持格式验证;

  • XML常用于配置文件网络消息传输等。

四.Java解析XML

XML是一种树形结构的文档,它有两种标准的解析API:

  • DOM一次性读取XML,并在内存中表示为树形结构
  • SAX:以流的形式读取XML,使用事件回调

1.解析DOM

1.1什么是DOM?

DOM是Document Object Model的缩写,DOM模型就是把XML结构作为一个树形结构处理,从根节点开始,每个节点都可以包含任意个子节点。

以下面的XML为例:

<?xml version="1.0" encoding="UTF-8" ?>
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>

如果解析为DOM结构,它大概为这样
在这里插入图片描述

  • 最顶层的document代表XML文档它是真正的“根”,而<book>虽然是根元素,但它是document的一个子节点

1.2.Java以DOM方式解析XML?

Java提供了DOM API来解析XML,它使用下面的对象来表示XML的内容:

  • Document:代表整个XML文档
  • Element:代表一个XML元素
  • Attribute:代表一个元素的某个属性

示例代码

public class TestXml {
    public static void printNode(Node n, int indent) {
        for (int i = 0; i < indent; i++) {
            System.out.print(' ');
        }
        switch (n.getNodeType()) {
            case Node.DOCUMENT_NODE: // Document节点
                System.out.println("Document: " + n.getNodeName());
                break;
            case Node.ELEMENT_NODE: // 元素节点
                System.out.println("Element: " + n.getNodeName());
                break;
            case Node.TEXT_NODE: // 文本
                System.out.println("Text: " + n.getNodeName() + " = " + n.getNodeValue());
                break;
            case Node.ATTRIBUTE_NODE: // 属性
                System.out.println("Attr: " + n.getNodeName() + " = " + n.getNodeValue());
                break;
            default: // 其他
                System.out.println("NodeType: " + n.getNodeType() + ", NodeName: " + n.getNodeName());
        }
        for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
            printNode(child, indent + 1);
        }
    }

    @Test
    public void testDom1() throws ParserConfigurationException, IOException, SAXException {
        InputStream input = TestXml.class.getResourceAsStream("/book.xml");
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(input);

        NodeList nodeList = document.getElementsByTagName("book");

        for (int i = 0; i < nodeList.getLength(); i++) {
            printNode(nodeList.item(i), i);
        }

    }
}

DocumentBuilder.parse()用于解析一个XML,它可以接收InputStream,File或者URL,如果解析无误将返回一个Document对象,这个对象代表了整个XML文档的树形结构 需要遍历以便读取指定元素的值

在这里插入图片描述

  • 对于DOM API解析出来的结构,我们从根节点Document出发,可以遍历所有子节点,获取所有元素属性文本数据,还可以包括注释,这些节点被统称为Node每个Node都有自己的类型Type根据Type来区分一个Node到底是元素,还是属性,还是文本

  • 使用DOM API时,如果要读取某个元素的文本,需要访问Type=Text类型的子节点,所以使用起来还是比较繁琐的。

1.3.实例代码

public class TestDom {
    @Test
    public void testDom() throws ParserConfigurationException, IOException, SAXException {
        //1.创建DocumentBuilderFactory对象
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //2.创建DocumentBuilder对象
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse("src/main/resources/book.xml");
            NodeList nodeList = document.getElementsByTagName("book");
            element(nodeList);
           // node(nodeList);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //用Element方式
    public static void element(NodeList list){
        for (int i = 0; i <list.getLength() ; i++) {
            Element element = (Element) list.item(i);
            NodeList childNodes = element.getChildNodes();
            for (int j = 0; j <childNodes.getLength() ; j++) {
                if (childNodes.item(j).getNodeType()== Node.ELEMENT_NODE) {
                    //获取节点
                    System.out.print(childNodes.item(j).getNodeName() + ":");
                    //获取节点值
                    System.out.println(childNodes.item(j).getFirstChild() == null ? "null" : childNodes.item(j).getFirstChild().getNodeValue());
                }
            }
        }
    }

    public static void node(NodeList list){
        for (int i = 0; i <list.getLength() ; i++) {
            Node node = list.item(i);
            NodeList childNodes = node.getChildNodes();
            for (int j = 0; j <childNodes.getLength() ; j++) {
                if ( childNodes.item(j).getNodeType()==Node.ELEMENT_NODE) {
                    System.out.print(childNodes.item(j).getNodeName() + ":");
                    System.out.println(childNodes.item(j).getFirstChild() == null ? "null" : childNodes.item(j).getFirstChild().getNodeValue());
                }
            }
        }
    }
}

1.4.DOM解析小结

  • Java提供的DOM API可以将XML解析为DOM结构,以Document对象表示;

  • DOM可在内存中完整表示XML数据结构

  • DOM解析速度慢,内存占用大。

2.解析SAX

以下面的XML为例:

<?xml version="1.0" encoding="UTF-8" ?>
<class>
    <student>
        <firstname>cxx1</firstname>
        <lastname>Bob1</lastname>
        <nickname>stars1</nickname>
        <marks>85</marks>
    </student>
    <student rollno="493">
        <firstname>cxx2</firstname>
        <lastname>Bob2</lastname>
        <nickname>stars2</nickname>
        <marks>85</marks>
    </student>
    <student rollno="593">
        <firstname>cxx3</firstname>
        <lastname>Bob3</lastname>
        <nickname>stars3</nickname>
        <marks>85</marks>
    </student>
</class>

2.1.是什么是SAX解析

  • 使用DOM解析XML的优点是用起来省事,但它的主要缺点是内存占用太大。

  • SAX是一种使用事件回调机制的XML解析器,事件由解析器产生并通过回调函数发送给应用程序,这种模式称为“推模式”

  • SAXSimple API for XML的缩写,它是一种基于流的解析方式,边读取XML边解析,并以事件回调的方式让调用者获取数据。 因为是一边读一边解析,所以无论XML有多大,占用的内存都很小。

SAX解析会触发一系列事件:

事件 描述
startDocument 开始读取XML文档
startElement 读取到了一个元素,例如<book>
characters 读取到了字符
endElement 读取到了一个结束的元素,例如</book>
endDocument 读取XML文档结束

在这里插入图片描述

public class TestSax {
    @Test
    public void testSax() throws ParserConfigurationException, SAXException, IOException {
        InputStream input = TestSax.class.getResourceAsStream("/book.xml");
        //1.获取SAXParserFactory实例
        SAXParserFactory spf = SAXParserFactory.newInstance();
        //2. 获取SAXparser实例
        SAXParser saxParser = spf.newSAXParser();
        //3.创建Handel对象并交给解析器
        saxParser.parse(input, new MyHandler());
    }
}

class MyHandler extends DefaultHandler {
    @Override
    public void startDocument() throws SAXException {
        print("start document");
    }
    @Override
    public void endDocument() throws SAXException {
        print("end document");
    }
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        print("start element:", localName, qName);
    }
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        print("end element:", localName, qName);
    }
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        print("characters:", new String(ch, start, length));
    }
    @Override
    public void error(SAXParseException e) throws SAXException {
        print("error:", e);
    }

    void print(Object... objs) {
        for (Object obj : objs) {
            System.out.print(obj);
            System.out.print(" ");
        }
        System.out.println();
    }
}

执行结果:
在这里插入图片描述
如果要读取<name>节点的文本,我们就必须在解析过程中根据startElement()endElement()定位当前正在读取的节点,可以使用栈结构保存每遇到一个startElement()入栈,每遇到一个endElement()出栈读到characters()时我们才知道当前读取的文本是哪个节点的。 可见使用SAX API仍然比较麻烦。

2.2.SAX小结

  • SAX是一种流式解析XML的API

  • SAX通过事件触发读取速度快消耗内存少

  • 调用方必须通过回调方法获得解析过程中的数据。

3.解析StAX

以下面的XML为例:

<?xml version="1.0" encoding="UTF-8" ?>
<class>
    <student>
        <firstname>cxx1</firstname>
        <lastname>Bob1</lastname>
        <nickname>stars1</nickname>
        <marks>85</marks>
    </student>
    <student rollno="493">
        <firstname>cxx2</firstname>
        <lastname>Bob2</lastname>
        <nickname>stars2</nickname>
        <marks>85</marks>
    </student>
    <student rollno="593">
        <firstname>cxx3</firstname>
        <lastname>Bob3</lastname>
        <nickname>stars3</nickname>
        <marks>85</marks>
    </student>
</class>

3.1.什么是StAX?

StAX与SAX类似,也是基于流式解析XML事件触发的模式,不过事件不同与SAX的回调通知方式,需要应用程序自行遍历判断事件类型,从中筛选出要获取的节点的信息,所有事件类型都在XMLStreamConstants中定义
常见的有:

字典 事件
XMLStreamConstants.START_ELEMENT 解析开标签节点事件
XMLStreamConstants.END_ELEMENT
闭标签节点事件
XMLStreamConstants.CHARACTERS
文本节点事件

StAX这种解析的策略也被成为“拉模式”
在这里插入图片描述

  • StAX首先要获取XML文档流对象,然后创建解析器工厂对象(XMLInputFactory),根据工厂对象创建解析器对象(XMLStreamReader)
    这里的解析器实际上就是一个迭代器,根据迭代器可以顺序获取事件类型,并根据事件类型去调用解析器的其他方法获取节点内容进行处理
public class TestStax {
    @Test
    public void testStax() throws Exception {
        InputStream in = TestStax.class.getResourceAsStream("/student.xml");
        XMLInputFactory factory = XMLInputFactory.newFactory();
        XMLStreamReader parser = factory.createXMLStreamReader(in);
        while (parser.hasNext()) {
            int event = parser.next();
            // 解析开标签
            if (event == XMLStreamConstants.START_ELEMENT) {
                System.out.println("解析标签元素: " + parser.getLocalName());
                int attCount = parser.getAttributeCount();
                System.out.println("该标签属性数量: " + attCount);
                for (int i = 0; i < attCount; i++) {
                    System.out.println("属性名:" + parser.getAttributeLocalName(0));
                    System.out.println("属性值:" + parser.getAttributeValue(0));
                }
                System.out.println();
                continue;
            }
            // 解析文本
            if (event == XMLStreamConstants.CHARACTERS) {
                System.out.println("文本内容: \"" + parser.getText() + "\"");
                System.out.println();
                continue;
            }
            // 解析闭标签
            if (event == XMLStreamConstants.END_ELEMENT) {
                System.out.println("解析标签元素结束: " + parser.getLocalName());
                System.out.println();
            }
        }
    }
}

执行结果
在这里插入图片描述

4.三种方式的比较

  • DOM 的优点在于面向节点树编程比较简单,也比较好理解,在解析DOM时就已经完整加载了文档树,对节点的遍历和导航(包括父节点、子节点、兄弟节点)比较方便,也易于添加和删除节点,但是在文档内容比较大的时候,性能消耗比较大,处理效率较低,不过一般都用作配置文件,内容不多,因此忽略不计。

如果xml文件本身内容较多,而且在很多情况下只想解析某一个节点而不想加载全部节点浪费资源,这时可考虑使用流机制的解析器SAX和StAX,能够降低性能消耗,提高效率。

  • SAX 的缺点非常明显,没有加载完整的文档结构,对节点信息的获取和处理依赖回调函数,当处理逻辑涉及多个多层节点之间的关系时,回调函数的逻辑会非常复杂和难以维护;而且流处理方式只允许从上往下处理,不允许回溯已经处理过的节点,另外SAX也不支持修改XML。

  • StAX 具有跟SAX一样的流处理的缺点,StAX包括了两套处理XML文档的API:一种是基于指针的API,效率高但是抽象化程度低;另一种是基于事件迭代器的API,效率低但是抽象化程序高。开发者可以根据需求做平衡和选择。

5.使用Jackson

以以下XML为例

<?xml version="1.0" encoding="UTF-8" ?>
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>

对应Java对象

public class Book {
    public long id;
    public String name;
    public String author;
    public String isbn;
    public List<String> tags;
    public String pubDate;
}

Jackson的开源的第三方库可以轻松做到XML到JavaBean的转换。我们要使用Jackson,先添加两个Maven的依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.10.1</version>
</dependency>
<dependency>
    <groupId>org.codehaus.woodstox</groupId>
    <artifactId>woodstox-core-asl</artifactId>
    <version>4.4.1</version>
</dependency>
public class TestJackson {
    @Test
    public void TestXMLToBean() throws IOException {
        InputStream input = TestJackson.class.getResourceAsStream("/book.xml");
        JacksonXmlModule module = new JacksonXmlModule();
        XmlMapper mapper = new XmlMapper(module);
        Book book = mapper.readValue(input, Book.class);
        System.out.println(book.id);
        System.out.println(book.name);
        System.out.println(book.author);
        System.out.println(book.isbn);
        System.out.println(book.tags);
        System.out.println(book.pubDate);
    }
}

class Book {
    public long id;
    public String name;
    public String author;
    public String isbn;
    public List<String> tags;
    public String pubDate;
}

执行结果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/104148269