XML 使用DTD(document type definition)文档类型来标记数据和定义数据,格式统一且跨平台和语言,已成为业界公认的标准。

目前 XML 描述数据龙头老大的地位渐渐受到 Json 威胁。经手项目中,模块/系统之间交互数据方式有 XML 也有 Json,说不上孰好孰坏。

XML 规整/有业界标准/很容易和其他外部的系统进行交互,Json 简单/灵活/占带宽比小。

仁者见仁智者见智,项目推进中描述数据方式需要根据具体场景拿捏。

这篇博客主要描述目前 Java 中比较主流的 XML 解析底层方式,给需要这方面项目实践的同学一些参考。

Demo 项目 git 地址:https://git.oschina.net/LanboEx/xml-parse-demo.git

Sax/Satx/Dom 由国外开源社区或组织贡献,Sun 重新组织起名 JAXP 自JDK 1.6 起陆续将他们添加进去。

Xmlpull在 JDK 中没有看到它的身影,如果需要使用它,你需要添加额外的类库。

Jdom/Dom4j/Xstream... 是基于这些底层解析方式重新组织封装的开源类库,提供简洁的 API,有机会再写一篇博客描述。

Dom4j 是基于 JAXP 解析方式,性能优异、功能强大、极易使用的优秀开源类库。

Jdom 如果你细看内部代码,其实也是基于 JAXP 但具体包结构被重新组织, API 大量使用了 Collections 类,在性能上被 dm4j 压了好几个档次。

实例 Demo 中需要解析的 xml 文件如下,中规中矩不简单,也不复杂,示例业务场景都能将节点的值解析出来,组成业务实体对象。

  1. <?xml version="1.0"?>
  2. <classGrid>
  3. <classGridlb>
  4. <class_id>320170105000009363</class_id>
  5. <class_number>0301</class_number>
  6. <adviser>018574</adviser>
  7. <studentGrid>
  8. <studentGridlb>
  9. <stu_id>030101</stu_id>
  10. <stu_name>齐天</stu_name>
  11. <stu_age>9</stu_age>
  12. <stu_birthday>2008-11-07</stu_birthday>
  13. </studentGridlb>
  14. <studentGridlb>
  15. <stu_id>030102</stu_id>
  16. <stu_name>张惠</stu_name>
  17. <stu_age>10</stu_age>
  18. <stu_birthday>2009-04-08</stu_birthday>
  19. </studentGridlb>
  20. <studentGridlb>
  21. <stu_id>030103</stu_id>
  22. <stu_name>龙五</stu_name>
  23. <stu_age>9</stu_age>
  24. <stu_birthday>2008-11-01</stu_birthday>
  25. </studentGridlb>
  26. </studentGrid>
  27. </classGridlb>
  28. <classGridlb>
  29. <class_id>420170105000007363</class_id>
  30. <class_number>0302</class_number>
  31. <adviser>018577</adviser>
  32. <studentGrid>
  33. <studentGridlb>
  34. <stu_id>030201</stu_id>
  35. <stu_name>马宝</stu_name>
  36. <stu_age>10</stu_age>
  37. <stu_birthday>2009-09-02</stu_birthday>
  38. </studentGridlb>
  39. </studentGrid>
  40. </classGridlb>
  41. </classGrid>

Demo.xml

1. 基于树或基于对象模型

官方 W3C 标准,以层次结构组织的节点或信息片断的集合。允许在树中寻找特定信息,分析该结构通常需要加载整个文档和构造层次结构。

最早的一种解析模型,加载整个文档意味着在大文件 XMl 会遇到性能瓶颈。

Dom 解析代码示例:

  1. DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();//得到Dom解析器的工厂实例
  2. DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();//从Dom工厂中获得Dom解析器
  3. Document doc = dbBuilder.parse(Thread.currentThread().getContextClassLoader().getResource("demo.xml").getPath());
  4. NodeList nList = doc.getElementsByTagName("studentGridlb");
  5.  
  6. List<StudentGridlb> studentGridlbList = new ArrayList<>();
  7. for (int i = 0; i < nList.getLength(); i++) {
  8. StudentGridlb studentGridlb = new StudentGridlb();
  9. NodeList childNodes = nList.item(i).getChildNodes();
  10. for (int k = 0; k < childNodes.getLength(); k++) {
  11. if (childNodes.item(k) instanceof Element) {
  12. Element element = (Element) childNodes.item(k);
  13. if ("stu_id".equals(element.getNodeName())) {
  14. studentGridlb.setStu_id(element.getTextContent());
  15. }
  16. if ("stu_name".equals(element.getNodeName())) {
  17. studentGridlb.setStu_name(element.getTextContent());
  18. }
  19. if ("stu_age".equals(element.getNodeName())) {
  20. studentGridlb.setStu_age(Integer.parseInt(element.getTextContent()));
  21. }
  22. if ("stu_birthday".equals(element.getNodeName())) {
  23. DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  24. studentGridlb.setStu_birthday(format.parse(element.getTextContent()));
  25. }
  26. }
  27. }
  28. studentGridlbList.add(studentGridlb);
  29. }

Dom 解析方式有个最大的优点可以在任何时候在树中上下导航,获取和操作任意部分的数据。

2. 流事件分析中推模型

靠事件驱动的模型,当它每发现一个节点就引发一个事件,需要编写这些事件的处理程序。

这样的做法很麻烦,而且不灵活,主流的分析方式有 Xmlpull和 JAXP 中的 Sax。

Xmlpulldemo (引入 xmlpull.jar  xpp3_min.jar]):

  1. XmlPullParserFactory pullParserFactory = XmlPullParserFactory.newInstance();
  2. XmlPullParser pullParser = pullParserFactory.newPullParser();//获取XmlPullParser的实例
  3. pullParser.setInput(Thread.currentThread().getContextClassLoader().getResourceAsStream("demo.xml"), "UTF-8");
  4.  
  5. int event = pullParser.getEventType();
  6. List<StudentGridlb> studentGridlbList = new ArrayList<>();
  7. StudentGridlb studentGridlb = new StudentGridlb();
  8.  
  9. while (event != XmlPullParser.END_DOCUMENT) {
  10. String nodeName = pullParser.getName();
  11. switch (event) {
  12. case XmlPullParser.START_DOCUMENT:
  13. System.out.println("Xmlpull解析 xml 开始:");
  14. break;
  15. case XmlPullParser.START_TAG:
  16. if ("stu_id".equals(nodeName)) {
  17. studentGridlb.setStu_id(pullParser.nextText());
  18. }
  19. if ("stu_name".equals(nodeName)) {
  20. studentGridlb.setStu_name(pullParser.nextText());
  21. }
  22. if ("stu_age".equals(nodeName)) {
  23. studentGridlb.setStu_age(Integer.parseInt(pullParser.nextText()));
  24. }
  25. if ("stu_birthday".equals(nodeName)) {
  26. DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  27. studentGridlb.setStu_birthday(format.parse(pullParser.nextText()));
  28. }
  29. break;
  30. case XmlPullParser.END_TAG:
  31. if ("studentGridlb".equals(nodeName)) {
  32. studentGridlbList.add(studentGridlb);
  33. studentGridlb = new StudentGridlb();
  34. }
  35. break;
  36. }
  37. event = pullParser.next();
  38. }

Xmlpull为接口层,xpp3_min 为实现层,其实可以引入另外自带接口层 xpp3 版本。

  1. <dependency>
  2. <groupId>xmlpull</groupId>
  3. <artifactId>xmlpull</artifactId>
  4. <version>1.1.3.1</version>
  5. </dependency>
  6.  
  7. <dependency>
  8. <groupId>xpp3</groupId>
  9. <artifactId>xpp3_min</artifactId>
  10. <version>1.1.4c</version>
  11. </dependency>

Sax demo:

  1. SaxParserFactory SaxParserFactory = SaxParserFactory.newInstance(); //获取Sax分析器的工厂实例,专门负责创建SaxParser分析器
  2. SaxParser SaxParser = SaxParserFactory.newSaxParser();
  3. InputStream inputStream = new FileInputStream(new File(Thread.currentThread().getContextClassLoader().getResource("demo.xml").getPath()));
  4.  
  5. SaxHandler xmlSaxHandler = new SaxHandler();
  6. SaxParser.parse(inputStream, xmlSaxHandler);

Sax 解析时还需要单独编写时间响应 Handler ,和集合排序时实现的Comparator 类似。

  1. public class SAXHandler extends DefaultHandler {
  2. private List<StudentGridlb> studentGridlbList = null;
  3. private StudentGridlb studentGridlb = null;
  4. private String tagName;
  5.  
  6. @Override
  7. public void startDocument() throws SAXException {
  8. System.out.println("---->startDocument() is invoked...");
  9. studentGridlbList = new ArrayList<>();
  10. }
  11.  
  12. @Override
  13. public void endDocument() throws SAXException {
  14. System.out.println("---->endDocument() is invoked...");
  15. }
  16.  
  17. @Override
  18. public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
  19. System.out.println("-------->startElement() is invoked...,qName:" + qName);
  20. if ("studentGridlb".equals(qName)) {
  21. this.studentGridlb = new StudentGridlb();
  22. }
  23. this.tagName = qName;
  24. }
  25.  
  26. @Override
  27. public void endElement(String uri, String localName, String qName) throws SAXException {
  28. System.out.println("-------->endElement() is invoked...");
  29. if (qName.equals("studentGridlb")) {
  30. this.studentGridlbList.add(this.studentGridlb);
  31. }
  32. this.tagName = null;
  33. }
  34.  
  35. @Override
  36. public void characters(char[] ch, int start, int length) throws SAXException {
  37. System.out.println("------------>characters() is invoked...");
  38.  
  39. if (this.tagName != null) {
  40. String contentText = new String(ch, start, length);
  41. if (this.tagName.equals("stu_id")) {
  42. this.studentGridlb.setStu_id(contentText);
  43. }
  44. if (this.tagName.equals("stu_name")) {
  45. this.studentGridlb.setStu_name(contentText);
  46. }
  47. if (this.tagName.equals("stu_age")) {
  48. this.studentGridlb.setStu_age(Integer.parseInt(contentText));
  49. }
  50. if (this.tagName.equals("stu_birthday")) {
  51. DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  52. try {
  53. this.studentGridlb.setStu_birthday(format.parse(contentText));
  54. } catch (ParseException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. }
  60.  
  61. public List<StudentGridlb> getStudentGridlbList() {
  62. return studentGridlbList;
  63. }
  64.  
  65. public void setStudentGridlbList(List<StudentGridlb> studentGridlbList) {
  66. this.studentGridlbList = studentGridlbList;
  67. }
  68. }

SaxHandler

推模式不需要等待所有数据都被处理,分析就能立即开始;

只在读取数据时检查数据,不需要保存在内存中;

可以在某个条件得到满足时停止解析,不必解析整个文档;

效率和性能较高,能解析大于系统内存的文档;

当然缺点也很突出例如需要自己负责TAG的处理逻辑(例如维护父/子关系等),使用麻烦;

单向导航,很难同时访问同一文档的不同部分数据,不支持 XPath;

3. 流事件分析中的拉模型

在遍历文档时,把感兴趣的部分从读取器中拉出,不需要引发事件,允许我们选择性地处理节点;

大大提高了灵活性,以及整体效率,拉模式中比较常见 stax,stax 提供了两套 API 共使用。

stax demo(基于光标的方式解析XML):

  1. InputStream stream = new FileInputStream(Thread.currentThread().getContextClassLoader().getResource("demo.xml").getPath());
  2. XMLInputFactory factory = XMLInputFactory.newInstance();
  3. XMLStreamReader parser = factory.createXMLStreamReader(stream);
  4. List<StudentGridlb> studentGridlbList = new ArrayList<>();
  5. StudentGridlb studentGridlb = null;
  6. while (parser.hasNext()) {
  7. int event = parser.next();
  8. if (event == XMLStreamConstants.START_DOCUMENT) {
  9. System.out.println("stax 解析xml 开始.....");
  10. }
  11. if (event == XMLStreamConstants.START_ELEMENT) {
  12. if (parser.getLocalName().equals("stu_id")) {
  13. studentGridlb = new StudentGridlb();
  14. studentGridlb.setStu_id(parser.getElementText());
  15. } else if (parser.getLocalName().equals("stu_name")) {
  16. studentGridlb.setStu_name(parser.getElementText());
  17. } else if (parser.getLocalName().equals("stu_age")) {
  18. studentGridlb.setStu_age(Integer.parseInt(parser.getElementText()));
  19. } else if (parser.getLocalName().equals("stu_birthday")) {
  20. DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  21. studentGridlb.setStu_birthday(format.parse(parser.getElementText()));
  22. }
  23. }
  24. if (event == XMLStreamConstants.END_ELEMENT) {
  25. if (parser.getLocalName().equals("studentGridlb")) {
  26. studentGridlbList.add(studentGridlb);
  27. }
  28. }
  29. if (event == XMLStreamConstants.END_DOCUMENT) {
  30. System.out.println("stax 解析xml 结束.....");
  31. }
  32. }
  33. parser.close();

stax demo(基于迭代方式解析XML):

  1. XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
  2. XMLEventReader xmlEventReader = xmlInputFactory.createXMLEventReader(Thread.currentThread().getContextClassLoader().getResourceAsStream("demo.xml"));
  3.  
  4. List<StudentGridlb> studentGridlbList = new ArrayList<>();
  5. StudentGridlb studentGridlb = null;
  6.  
  7. while (xmlEventReader.hasNext()) {
  8. XMLEvent event = xmlEventReader.nextEvent();
  9. if (event.isStartElement()) {
  10. String name = event.asStartElement().getName().toString();
  11. if (name.equals("stu_id")) {
  12. studentGridlb = new StudentGridlb();
  13. studentGridlb.setStu_id(xmlEventReader.getElementText());
  14. } else if (name.equals("stu_name")) {
  15. studentGridlb.setStu_name(xmlEventReader.getElementText());
  16. } else if (name.equals("stu_age")) {
  17. studentGridlb.setStu_age(Integer.parseInt(xmlEventReader.getElementText()));
  18. } else if (name.equals("stu_birthday")) {
  19. DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  20. studentGridlb.setStu_birthday(format.parse(xmlEventReader.getElementText()));
  21. }
  22. }
  23.  
  24. if (event.isEndElement()) {
  25. String name = event.asEndElement().getName().toString();
  26. if (name.equals("studentGridlb")) {
  27. studentGridlbList.add(studentGridlb);
  28. }
  29. }
  30. }
  31. xmlEventReader.close();

基于指针的 stax API,这种方式尽管效率高,但没有提供 XML 结构的抽象,因此是一种低层 API。

stax 基于迭代器的 API 是一种面向对象的方式,这也是它与基于指针的 API 的最大区别。

基于迭代器的 API 只需要确定解析事件的类型,然后利用其方法获得属于该事件对象的信息。

通过将事件转变为对象,让应用程序可以用面向对象的方式处理,有利于模块化和不同组件之间的代码重用。

Ok,这篇博客对 java 底层解析 xml 方式做了点总结。其实在实际项目中,上述几种方式解析 xml 编写起来都很费事。

都会引入封装起来的稳定开源库,如 Dom4j/Jdom/Xstream.....,这些类库屏蔽了底层复杂的部分,呈现给我们简洁明了的 API;

但如果公司业务复杂程度已经远远超出了开源类库的提供的范畴,不妨自己依赖底层解析技术自己造轮子。

浅谈 Java Xml 底层解析方式的更多相关文章

  1. 浅谈 Java 主流开源类库解析 XML

    在大型项目编码推进中,涉及到 XML 解析问题时,大多数程序员都不太会选用底层的解析方式直接编码. 主要存在编码复杂性.难扩展.难复用....,但如果你是 super 程序员或是一个人的项目,也不妨一 ...

  2. !! 浅谈Java学习方法和后期面试技巧

    浅谈Java学习方法和后期面试技巧 昨天查看3303回复33 部落用户大酋长 下面简单列举一下大家学习java的一个系统知识点的一些介绍 一.java基础部分:java基础的时候,有些知识点是非常重要 ...

  3. 浅谈java类集框架和数据结构(2)

    继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...

  4. 浅谈Java线程安全

    浅谈Java线程安全 - - 2019-04-25    17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...

  5. 浅谈Java中set.map.List的区别

    就学习经验,浅谈Java中的Set,List,Map的区别,对JAVA的集合的理解是想对于数组: 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),JAVA集合可以存储和操 ...

  6. Java基础学习总结(29)——浅谈Java中的Set、List、Map的区别

    就学习经验,浅谈Java中的Set,List,Map的区别,对JAVA的集合的理解是想对于数组: 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),JAVA集合可以存储和操 ...

  7. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  8. 浅谈Java中的对象和引用

    浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...

  9. 浅谈JAVA集合框架

    浅谈JAVA集合框架 Java提供了数种持有对象的方式,包括语言内置的Array,还有就是utilities中提供的容器类(container classes),又称群集类(collection cl ...

随机推荐

  1. thinkphp3.2.x多图上传并且生成多张缩略图

    html部分 <!DOCTYPE html><html><head><meta http-equiv="Content-Type" con ...

  2. JavaScript 数组操作方法

    这些数组的操作方法会改变原来的数组.在使用 Vue 或者 Angular 等框架的时候会非常实用,使用这些方法修改数组会触发视图的更新. Array.prototype.push 该方法可以在数组末尾 ...

  3. jquery处理checkbox(复选框)是否被选中

    现在如果一个复选框被选中,是用checked=true,checked="checked"也行 要用prop代替attr会更好,虽然在jQuery1.6之前版本的attr()方法能 ...

  4. JavaScript基础学习(六)—函数

    一.函数的定义 1.function语句形式 //1.function语句式 function test1(){ alert("I am test1"); } test1(); 2 ...

  5. 职责链模式(Chain of Responsibility)的Java实现

    职责链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它 ...

  6. windows编程初步

    #include <windows.h> const char g_szClassName[] = "myWindowClass"; LRESULT CALLBACK ...

  7. APP被苹果App Store拒绝的79个原因【转】

    作为iOS开发者,估计有很多都遇到过APP提交到App Store被拒,然后这些被拒的原因多种多样,今天dApps收集了常见的被拒的原因,以便更多开发者了解. APP被苹果APPStore拒绝的各种原 ...

  8. 关于ajax post请求跨域问题的解决心得

    最近啊,公司有个项目,需要做一个手机端APP的后台管理系统.所以用到了度文本编辑框,经过了好好一番周折,终于弄好了,带到上线的时候发现啊,只能使用ip去访问网页的时候上能穿图片他不会报跨域的问题,而使 ...

  9. 构造函数与普通函数的区别还有关于“new”操作符的一些原理

    有一种创建对象的方法叫做工厂模式,例如: function person(name,age){ var o=new Object(); o.name=name; o.age=age; return o ...

  10. 基础SELECT示例掌握

    SELECT查询语句 ---进行单条记录.多条记录.单表.多表.子查询-- SELECT [ALL | DISTINCT | DISTINCTROW ] [HIGH_PRIORITY] [MAX_ST ...