XML文件与Properties文件在我的Java学习中和以后要接触的框架中十分常见的文件类型。这些文件懂了相关的编写规则后会十分容易写出来,但是作为配置文件如何去用它才是关键。人根据自己要完成的目的编写相关文件,可是你把这个文件放在计算机面前它不懂啊,它只知道01序列。因此,对于这种高频出现的文件,我们有必要编程,编写一个工具来解析它,方便以后使用。我不是直接把相关解析工具代码直接贴出来,而是一步步写关于这个工具如何造出来,这样才能学习更深刻并且里面蕴含的工具思想也能写出来。
XML文件解析工具
初识XML
XML(Extensible Markup Language),中文翻译为可扩展标记语言。它可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。(以上摘自百度百科)使用它我们可以很容易的描述复杂的层次数据结构。例如,下面给的一个XML文件简单例子,学生基本信息。
<?xml version="1.0" encoding="UTF-8"?>
<students>
<student name= "张三" id= "20203035" sex= "男">
<hobbies>
<hobby>打游戏</hobby>
<hobby>篮球</hobby>
<hobby>学习</hobby>
</hobbies>
</student>
<student name= "李四" id= "20203036" sex= "男">
<hobbies>
<hobby>足球</hobby>
<hobby>写作</hobby>
<hobby>摄影</hobby>
</hobbies>
</student>
<student name= "王五" id= "20203037" sex= "女">
<hobbies>
<hobby>购物</hobby>
<hobby>逛街</hobby>
<hobby>羽毛球</hobby>
</hobbies>
</student>
</students>
只是个简单的学生信息XML文件。对XML文件稍有了解,知道<></>是标签,标签里面带着的叫做属性。分析上面XML文件知,根标签是students(根标签必须有),下一个标签student,其中有三个属性(name, id, sex),下一个标签是hobbies,然后下一个标签是hobby。看到这个XML文件有没有觉得它特别像树形结构。因此,我们使用四种XML解析方式之一的DOM解析。
public class Test {
public static void main(String[] args) {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream is = Test.class.getResourceAsStream("/studentInfo.xml");
//这里是用当前类的class.getResourceAsStream。参数是相对路径。
Document document = documentBuilder.parse(is);
//前面四句话是不是看的一头雾水?我最初学也是,这里没必要刨根问底,当作巫师的咒语吧!
NodeList studentList = document.getElementsByTagName("student");
//根据标签名得到一个结点列表
for (int i = 0; i < studentList.getLength(); i++) {
StudentInfo stu = new StudentInfo();
//StudentInfo类只是为了最后显示好看。
Element student = (Element) studentList.item(i);
//要把Node类型强转为Element类型
String name = student.getAttribute("name");
String id = student.getAttribute("id");
String sex = student.getAttribute("sex");
stu.setId(id);
stu.setSex(sex);
stu.setName(name);
NodeList hobbyList = student.getElementsByTagName("hobby");
for (int j = 0; j < hobbyList.getLength(); j++) {
String hobbyString = hobbyList.item(j).getTextContent();
stu.addHobby(hobbyString);
}
System.out.println(stu);
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
要用到的model类
public class StudentInfo {
private String id;
private String name;
private String sex;
private List<String> hobbies;
public StudentInfo() {
hobbies = new ArrayList<>();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public List<String> getHobbies() {
return hobbies;
}
public void addHobby(String hobby) {
if (hobby == null) {
return;
}
if (hobbies.contains(hobby)) {
return;
}
hobbies.add(hobby);
}
@Override
public String toString() {
StringBuffer stu = new StringBuffer();
stu.append(name).append(" ")
.append(id).append(" ")
.append(sex).append(" ");
for (String hobby : hobbies) {
stu.append("\n").append("\t").append(hobby);
}
return stu.toString();
}
}
运行结果
张三 20203035 男
打游戏
篮球
学习
李四 20203036 男
足球
写作
摄影
王五 20203037 女
购物
逛街
羽毛球
关于NodeList,Node,Element这几个类我需要说一下。
getElementsByTagName(“标签名”)获得的是个NodeList,没办法强转成Element。但可以遍历这个NodeList,item(index)方法取得相应的一个Node。Node与Element之间有关系。
对于一个出现多次的标签(student),我们获得一个NodeList,遍历它,如果又遇到出现多次的标签(hobby),再获得一个NodeList,遍历它,如此下去,直到所有标签都被过一遍。
总结:NodeList是标签多次出现组成,最终要访问到具体内容还是要提供Element,NodeList与Element之间转换通过Node完成。
解析XML工具
所谓工具就是工具制造者把那些重复的、一成不变的代码提取出来做成工具,而那些会变部分的代码向外提供,由工具使用者去实现。就比如现在给你的不是这个学生信息XML表了,而换了另一个XML让你解析,再看解析代码,发现for循环上面的都是一成不变的(除了参数),而底下的真正解析过程会随着不同XML变化。
以后处理XML文件,在结构上任何XML文件都是相同的。只是在细节上标签名或者属性名有所差别而已。相同之处做成工具,造工具的人只编写XML相同的,结构相似的部分;差别之处拎出来提供给外面,让工具使用者去完成。
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public abstract class XMLParse {
private static final DocumentBuilderFactory dbf;
private static DocumentBuilder db;
static {
dbf = DocumentBuilderFactory.newInstance();
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
public XMLParse() {
}
public static Document getDocument(InputStream is) {
Document document = null;
try {
document = db.parse(is);
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return document;
}
public static Document getDocument(String xmlPath) {
InputStream is = XMLParse.class.getResourceAsStream(xmlPath);
if (is == null) {
System.out.println("xmlPath[" +xmlPath + "不存在");
return null;
}
return getDocument(is);
}
/*getElementsByTagName写了两次,但两次前缀不一样,一个是doucument,一个是element类型的,覆盖就行了*/
public void parseTag(Document document, String tagName) {
NodeList nodeList = document.getElementsByTagName(tagName);
for (int index = 0; index < nodeList.getLength(); index++) {
Element element = (Element) nodeList.item(index);
//底下的内容不同的xml解析不尽相同,工具写到这没法写了。
dealElement(element, index);
}
}
public void parseTag(Element element, String tagName) {
NodeList nodeList = element.getElementsByTagName(tagName);
for (int index = 0; index < nodeList.getLength(); index++) {
Element ele = (Element) nodeList.item(index);
//底下的内容不同的xml解析不尽相同,工具写到这没法写了。
//后面对ele进一步解析:取属性,取textContent或进一步取Element都应该是未来的工具使用者操心的事,我要做的仅仅是是把ele交给对方
dealElement(ele, index);
}
}
public abstract void dealElement(Element element, int index);
//这个index让用户知道处理到第几项都能知道
}
XML文件解析工具就写出来了。还是那句话将那些已经确定了的代码写好,对于以后操作者如何去用我不管,我也管不着,所以将它传给外面,尽量将有用的信息都传出去,因为这样使用工具的人才能多一些机会做自己的操作。
如何去用这个工具下面给个例子。
public class LocalTest {
public static void main(String[] args) {
new XMLParse() { //直接用匿名内部类去使用,方便快捷。
@Override
public void dealElement(Element element, int index) {//循环都不用写了,因为这句话就在循环里
StudentInfo stu = new StudentInfo();
String name = element.getAttribute("name"); //此时这个element就是标签student
String id = element.getAttribute("id");
String sex = element.getAttribute("sex");
stu.setId(id);
stu.setSex(sex);
stu.setName(name);
new XMLParse() {
@Override
public void dealElement(Element element, int index) {
//始终记得的这个是在循环里
//可直接再使用匿名内部类解析下一个标签。也可以新建方法解析把这层提出去,这样可读性更强,且不会造成代码逻辑混乱。
String hobbyString = element.getTextContent();
stu.addHobby(hobbyString);
}
}.parseTag(element, "hobby");
System.out.println(stu);
}
}.parseTag(XMLParse.getDocument("/studentInfo.xml"), "student");
}
}
Properties文件解析工具
Properties文件就十分简单了,它的格式主要是以key-value键值对的形式存储,所以它的工具类就十分好建立了。既然是个键值对,就放在Map数据结构中,要遵循Map的规则(键值最好不要重复)。
下面给个简单的properties文件例子,该例子是我连接数据库的相关配置。
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mec_javase_201910?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
user=root
password=123456
其工具类因为它是键值对形式,所以放在一个大池子里,解析一个就放进去一个,想用就从池子取。
public class PropertiesParse {
private static final Map<String, String> pool;
static {
pool = new HashMap<>();
}
private static void loadding(InputStream is) {
Properties properties = new Properties();
try {
InputStreamReader isr = new InputStreamReader(is, "utf-8");
/*乱码问题很有可能是输入流为其他格式的流的原因,解决方法强制转为utf-8流。*/
properties.load(isr);
Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = properties.getProperty(key);
pool.put(key, value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void loadCfgPath(String path) {
InputStream is = PropertiesParse.class.getResourceAsStream(path);
if (is == null) {
System.out.println("Path[" +path + "不存在");
return;
}
loadding(is);
}
public static void loadCfgAbsolutePath(String path) {
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(path));
loadding(is);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static String value(String key) {
return pool.get(key);
}
public static Set<String> getKeySet() {
return pool.keySet();
}
}
工具使用演示
public class PoropertiesTest {
public static void main(String[] args) {
ProperitiesParse.loadProperities("/connection.properties");
System.out.println(ProperitiesParse.value("driver"));
System.out.println(ProperitiesParse.value("url"));
System.out.println(ProperitiesParse.value("user"));
System.out.println(ProperitiesParse.value("password"));
}
}
/*
演示结果
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/mec_javase_201910?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
root
123456
*/
总结
至此,两个常用文件的简单解析工具就完成了。XML解析工具工具思想多一些,可以着重看也可以跟着敲一遍,才能收获更深。
XMLParse工具的补充
在实际使用这个工具的时候,我遇到过这样一种XML文件。
<?xml version="1.0" encoding="UTF-8"?>
<menu_bar>
<Separator></Separator>
<Separator></Separator>
<Separator></Separator>
<Separator></Separator>
<menuItem caption="exit"></menuItem>
<menu caption= "File">
<menu caption="New">
<menuItem caption = "Class"></menuItem>
<menuItem caption = "Package"></menuItem>
<menuItem caption = "Interface"></menuItem>
</menu>
<menuItem caption="Save"></menuItem>
<Separator></Separator>
<menuItem caption="Exit"></menuItem>
</menu>
<menu caption= "Edit">
<menuItem caption="Copy"></menuItem>
<menuItem caption="Cut"></menuItem>
<menuItem caption="Paste"></menuItem>
</menu>
<menu caption= "Format">
<menu caption="Font">
<menu caption="MicroSoft">
<menuItem caption="MFour"></menuItem>
<menuItem caption="MFive"></menuItem>
</menu>
<menu caption="Song">
<menuItem caption="SFour"></menuItem>
<menuItem caption="SFive"></menuItem>
</menu>
</menu>
<menuItem caption="AutoNewLine"></menuItem>
</menu>
<menu caption= "Option">
<menuItem caption="AddAFrame"></menuItem>
</menu>
<menu caption="Help">
<menuItem caption="About"></menuItem>
</menu>
</menu_bar>
我们可以看到menu标签,里面还有menu标签。如果用我们上面的工具以标签名进行解析的,那就不正确了。因此解析XML文件不仅要可以按标签名称解析,还需要可以按层次解析。
修改工具如下
public abstract class XMLParse {
private static final DocumentBuilderFactory dbf;
private static DocumentBuilder db;
static {
dbf = DocumentBuilderFactory.newInstance();
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
public XMLParse() {
}
public static Document getDocument(InputStream is) {
Document document = null;
try {
document = db.parse(is);
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return document;
}
public static Document getDocument(String xmlPath) {
InputStream is = XMLParse.class.getResourceAsStream(xmlPath);
if (is == null) {
System.out.println("xmlPath[" +xmlPath + "不存在");
return null;
}
return getDocument(is);
}
public static Document getDocumentByAbsolutePath(String xmlPath) {
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(xmlPath));
return getDocument(is);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
public void parseTag(Document document, String tagName) {
NodeList nodeList = document.getElementsByTagName(tagName);
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
dealElement(element, i);
}
}
public void parseTag(Element element, String tagName) {
NodeList nodeList = element.getElementsByTagName(tagName);
for (int i = 0; i < nodeList.getLength(); i++) {
Element ele = (Element) nodeList.item(i);
dealElement(ele, i);
}
}
public abstract void dealElement(Element element, int index);
public void parseRoot(Document document) { //开始从根标签开始解析
Element root = (Element) document.getChildNodes().item(0);
dealElement(root, 0);
}
public void parseElement(Element element) { //从根解析后才可以按层解析
NodeList nodeList = element.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) { //为什么有这个?因为确保他是element类型就是一行,输出还有其他东西那些不要。
dealElement((Element) node, i);
}
}
}
}