引言:我写本文的宗旨在于给需要使用XML,而又对XML不是很熟悉的人们提供一种使用思路,而不没有给出具体的使用方法,至于下文中提到的使用方法,还未尝试过,都是从网上整理而来!
什么是XML?
xm是l可扩展标记语言缩写,xml是互联网数据传输的重要工具,它可以跨越互联网任何的平台,不受编程语言和
操作系统的限制,可以说它是一个拥有互联网最高级别通行证的数据携带者。xml是当前处理结构化文档信息中相当
给力的技术,xml有助于在服务器之间穿梭结构化数据,这使得开发人员更加得心应手的控制数据的存储和传输。
什么是DTO?
DTD的作用是定义XML的合法构建模块,它使用一系列的合法元素来定义文档结构。
什么是XML Schema?
XML Schema是对XML文档结构的定义和描述,其主要的作用是用来约束XML文件,并验证XML文件有效性。
由于XML中可以使用自己定义的元素,由于是自己定义的,当给别人使用的时候,别人怎么知道你的XML文件中写是什么东西呢?
这就需要在XML Scheme文件中声明你自己定义了些什么元素,这些元素都有些什么属性。
Schema与DTO的区别:
(1) Schema本身也是XML文档,DTD定义跟XML没有什么关系,Schema在理解和实际应用有很多的好处。
(2) DTD文档的结构是“平铺型”的,如果定义复杂的XML文档,很难把握各元素之间的嵌套关系;Schema文档结构性强,各元素之间的嵌套关系非常直观。
(3) DTD只能指定元素含有文本,不能定义元素文本的具体类型,如字符型、整型、日期型、自定义类型等。Schema在这方面比DTD强大。
(4) Schema支持元素节点顺序的描述,DTD没有提供无序情况的描述,要定义无序必需穷举排列的所有情况。Schema可以利用xs:all来表示无序的情况。
(5) 对命名空间的支持。DTD无法利用XML的命名空间,Schema很好满足命名空间。并且,Schema还提供了include和import两种引用命名空间的方法。
SAX解析:
SAX,全称Simple API for XML,是一种以事件驱动的XMl API,是XML解析的一种新的替代方法,解析XML常用的还有DOM解析,PULL解析(Android特有),SAX与DOM不同的是它边扫描边解析,自顶向下依次解析,由于边扫描边解析,所以它解析XML具有速度快,占用内存少的优点,对于Android等CPU资源宝贵的移动平台来说是一个巨大的优势。
Java JDK自带的解析(SAXParserFactory SAXPaeser DefaultHandler)
特点: 一行一行的往下面执行解析的
二、使用步骤及案例
1. SAX解析步骤
解析步骤: 1.创建一个SAXParserFactory对象 SAXParserFactory factory=SAXParserFactory.newInstance(); 2.获得解析器 SAXParser parser=factory.newSAXParser(); 3.调用解析方法解析xml,这里的第一个参数可以传递文件、流、字符串、需要注意第二个参数(new DefaultHander) File file=new File("girls.xml"); parser.parse(file,new DefaultHandler()); /**注解:--->这里的DefaultHandler表示 DefaultHandler类是SAX2事件处理程序的默认基类。它继承了EntityResolver、DTDHandler、 ContentHandler和ErrorHandler这四个接口。包含这四个接口的所有方法,所以我们在编写事件处理程序时, 可以不用直接实现这四个接口,而继承该类,然后重写我们需要的方法,所以在这之前我们先定义一个用于实现解析 方法如下:*/ 4.创建一个MyHandler类来继承DefaultHandler并重写方法 //定一个名为MyHandler类用来继承DefaultHandler (1)MyHandler extends DefaultHander (2)重写方法,快速记住方法(2个开始,2个结束,1一个文字(charactor--里面的内容)) (3)2个开始:StartDocment(文档的开始)StartElement(元素的开始) 2个结束:endElement(元素的结束) endDocment(文档的结束,标志着xml文件的结束) 1个文字内容:charactor(文字内容) 5.创建一个集合把所解析的内容添加到集合 //分析:目的我们只是需要把xml里面的文字内容添加到我们的集合而不需要其他元素,所以我们需要进行判断得到 //(接上)我们需要的内容(下面会赋一个图帮助理解) 6.接步骤三 输出集合System.out.pritnln(list); 解析完成!
解析流程图:
(1) 待解析的xml
<?xml version="1.0" encoding="utf-8" ?> <girls> <girl id="1"> <name>黄梦莹</name> <age>32</age> </girl> <girl id="2"> <name>刘亦菲</name> <age>33</age> </girl> </girls>
(2) 用于封装数据的实体
/** * 封装实体 * * @author zls * @date 2020/3/19 */ @Data public class Girl { private String id; private String name; private String age; }
(3) 自定义解析xml的类
package sax; import lombok.Data; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * 自定义xml文件解析的实现类 * 原理:sax是一行一行的往下面执行解析的 * * 参考:https://blog.csdn.net/lsh364797468/article/details/51325540 * @author zls * @date 2020/3/19 */ @Data public class MyHandler extends DefaultHandler { // 准备一个用于添加xml数据的集合、调用Girl类、准一个用于用来保存开始的标签的tag private List<Girl> girls; private Girl girl; private String tag; // 记录xml中元素表中,因为是一行一行的解析的,所以需要记录 /** * 文档的开始 * @throws SAXException */ @Override public void startDocument() throws SAXException { super.startDocument(); // 因为这个方法只调用一次,所以在开始的时候就可以实例化集合 girls = new ArrayList<>(); } /** * 文档结束 * @throws SAXException */ @Override public void endDocument() throws SAXException { super.endDocument(); } /** * 元素的开始 * @param uri * @param localName * @param qName 每次遍历取得的标签,每次只取一行 * @param attributes * @throws SAXException */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); // 这个方法,只有当开始一个元素的时候才会调用, // 通过分析,当外部开始元素为girl的时候,需要将girl实例化 // 将tag赋值 tag = qName; if ("girl".equals(qName)) { girl = new Girl(); } } /** * 元素的结束 * @param uri * @param localName * @param qName * @throws SAXException */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); // 这句话,必须写,因为,当sax解析完一个元素的时候,会自动认为换行符是一个字符,会继续执行 character 方法 。如果不写,就会造成没有数据的现象。 tag = ""; // 这个方法,当到了元素结尾的时候,会调用,应该在这里,将对象添加到集合里面去。 if ("girl".equals(qName)) { girls.add(girl); } } /** * 文字内容 * @param ch * @param start * @param length * @throws SAXException */ @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); // 这里是内容,但是,无法直接判断属于哪一个元素。 String string = new String(ch, start, length); //这两种情况,表示 当前语句执行在 girls 标签内。 if ("name".equals(tag)) {//判断当前内容,属于哪一个元素。 girl.setName(string); } else if ("age".equals(tag)) { girl.setAge(string); } // try { // setFiledValue(tag, string); // } catch (IllegalAccessException e) { // e.printStackTrace(); // } } /** * 使用反射的方式设置字段的值 * * (1) 字段很多的情况 * 问题: * 如果xml中的标签很多,即:类中的属性很多,那么采用以上的方式,难道要写十万个if吗? * 所以这里采用反射的方式,自动的给对象的属性设置上值(学了那么多年的反射终于有了用武之地了)。 * * (2) 类中的属性和xml中的标签有点不一样的情况 * 问题:我遇到的情况是第三方提供的xml里面所有标签都是大写字母,而我们项目字段采用的驼峰命名法,那么如果直接把xml中的标签 * 定义为属性名,那多难看啊,几个单词连在一起都是大写,除了开发的人员自己能看懂以外,以后维护的人员看到了肯定会问候你祖上十八代的。 * 解决:为此,我采用了驼峰命名的方式给实体属性命名,反射的时候可以转换为大写字母和xml中的标签比较 * * (3) 除了用代理的方式,还可以用json转对象的方式 * 我们可以手动的拼接成一个json字符串,然后再采用json转对象的方式也是可以的。这种方式理论上也是可以的,有兴趣可以自己实现以下。 * @param tag * @param value */ public void setFiledValue(String tag, String value) throws IllegalAccessException { if (girl == null) { return; } Field[] declaredFields = girl.getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { if (Objects.equals(declaredField.getName(), tag)) { // 修改字段访问控制权限(字段是私有的,则需要设为可以访问) if(!declaredField.isAccessible()){ declaredField.setAccessible(true); } // 字段类型 String fieldType = declaredField.getType().getSimpleName(); System.out.println("字段:"+ declaredField.getName()+", 字段类型为: " + fieldType); declaredField.set(girl, value); } } } }