复杂XML结构通常包含多层嵌套节点、自定义属性、命名空间定义以及重复出现的同类节点,解析时需要先明确XML的整体结构,再根据业务需求选择合适的解析方式,避免解析过程中出现数据遗漏或者解析效率低下的问题。

复杂XML结构的常见特征
了解复杂XML的特征是解析的前提,常见的复杂特征包括以下几种:
- 节点嵌套层级超过3层,比如根节点下包含多个子节点,子节点下还有更深层的子节点
- 节点带有自定义属性,比如<user id="1001" level="vip">这样的节点,属性值也需要提取
- 包含XML命名空间,比如根节点定义了xmlns:ns="http://ippipp.com/ns",子节点使用ns前缀
- 存在大量重复的同名节点,比如多个<order>节点依次排列在父节点下
常用的XML解析方式对比
不同的解析方式适用于不同的复杂XML场景,以下是三种主流解析方式的特点对比:
| 解析方式 | 解析原理 | 适用场景 | 优缺点 |
|---|---|---|---|
| DOM解析 | 将整个XML文档加载到内存,生成树形结构 | XML文件较小,需要随机访问节点 | 优点是可以灵活操作节点,缺点是内存占用高 |
| SAX解析 | 基于事件驱动,逐行读取XML触发对应事件 | XML文件较大,只需要顺序读取数据 | 优点是内存占用低,缺点是不能随机访问节点 |
| StAX解析 | 基于拉模式,程序主动获取解析事件 | 需要灵活控制解析流程的复杂XML | 优点是可控性强,缺点是代码编写相对复杂 |
DOM解析复杂XML示例
DOM解析适合处理结构复杂但文件不大的XML,以下是使用Java原生DOM解析复杂XML的示例,假设待解析的XML内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<company xmlns:dept="http://ipipp.com/dept">
<dept:department id="d001">
<name>研发部</name>
<employees>
<employee id="e001">
<name>张三</name>
<age>28</age>
<skills>
<skill>Java</skill>
<skill>XML</skill>
</skills>
</employee>
<employee id="e002">
<name>李四</name>
<age>30</age>
<skills>
<skill>Python</skill>
</skills>
</employee>
</employees>
</dept:department>
</company>
解析上述XML的Java代码如下:
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.namespace.NamespaceContext;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
public class DomXmlParser {
public static void main(String[] args) throws Exception {
// XML内容字符串
String xmlContent = "<?xml version="1.0" encoding="UTF-8"?>n" +
"<company xmlns:dept="http://ipipp.com/dept">n" +
" <dept:department id="d001">n" +
" <name>研发部</name>n" +
" <employees>n" +
" <employee id="e001">n" +
" <name>张三</name>n" +
" <age>28</age>n" +
" <skills>n" +
" <skill>Java</skill>n" +
" <skill>XML</skill>n" +
" </skills>n" +
" </employee>n" +
" <employee id="e002">n" +
" <name>李四</name>n" +
" <age>30</age>n" +
" <skills>n" +
" <skill>Python</skill>n" +
" </skills>n" +
" </employee>n" +
" </employees>n" +
" </dept:department>n" +
"</company>";
// 创建DOM解析器工厂
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 开启命名空间支持
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
// 解析XML内容
Document document = builder.parse(new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8)));
// 获取根节点
Element root = document.getDocumentElement();
// 处理命名空间,获取dept前缀对应的节点
NodeList deptNodes = root.getElementsByTagNameNS("http://ipipp.com/dept", "department");
for (int i = 0; i < deptNodes.getLength(); i++) {
Node deptNode = deptNodes.item(i);
if (deptNode.getNodeType() == Node.ELEMENT_NODE) {
Element deptElement = (Element) deptNode;
// 获取department节点的id属性
String deptId = deptElement.getAttribute("id");
System.out.println("部门ID:" + deptId);
// 获取部门名称节点
NodeList nameNodes = deptElement.getElementsByTagName("name");
if (nameNodes.getLength() > 0) {
System.out.println("部门名称:" + nameNodes.item(0).getTextContent());
}
// 获取所有employee节点
NodeList employeeNodes = deptElement.getElementsByTagName("employee");
for (int j = 0; j < employeeNodes.getLength(); j++) {
Node empNode = employeeNodes.item(j);
if (empNode.getNodeType() == Node.ELEMENT_NODE) {
Element empElement = (Element) empNode;
String empId = empElement.getAttribute("id");
System.out.println(" 员工ID:" + empId);
// 获取员工姓名
NodeList empNameNodes = empElement.getElementsByTagName("name");
if (empNameNodes.getLength() > 0) {
System.out.println(" 员工姓名:" + empNameNodes.item(0).getTextContent());
}
// 获取员工年龄
NodeList ageNodes = empElement.getElementsByTagName("age");
if (ageNodes.getLength() > 0) {
System.out.println(" 员工年龄:" + ageNodes.item(0).getTextContent());
}
// 获取员工技能
NodeList skillNodes = empElement.getElementsByTagName("skill");
System.out.print(" 员工技能:");
for (int k = 0; k < skillNodes.getLength(); k++) {
System.out.print(skillNodes.item(k).getTextContent() + " ");
}
System.out.println();
}
}
}
}
}
}
SAX解析复杂XML示例
当XML文件较大时,DOM解析会占用过多内存,此时可以使用SAX解析,以下是解析上述XML的SAX示例:
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
public class SaxXmlParser {
public static void main(String[] args) throws Exception {
String xmlContent = "<?xml version="1.0" encoding="UTF-8"?>n" +
"<company xmlns:dept="http://ipipp.com/dept">n" +
" <dept:department id="d001">n" +
" <name>研发部</name>n" +
" <employees>n" +
" <employee id="e001">n" +
" <name>张三</name>n" +
" <age>28</age>n" +
" <skills>n" +
" <skill>Java</skill>n" +
" <skill>XML</skill>n" +
" </skills>n" +
" </employee>n" +
" <employee id="e002">n" +
" <name>李四</name>n" +
" <age>30</age>n" +
" <skills>n" +
" <skill>Python</skill>n" +
" </skills>n" +
" </employee>n" +
" </employees>n" +
" </dept:department>n" +
"</company>";
SAXParserFactory factory = SAXParserFactory.newInstance();
// 开启命名空间支持
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
// 自定义处理器
DefaultHandler handler = new DefaultHandler() {
private boolean isDeptName = false;
private boolean isEmpName = false;
private boolean isEmpAge = false;
private boolean isSkill = false;
private String currentDeptId = null;
private String currentEmpId = null;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// 处理department节点
if ("department".equals(localName) && "http://ipipp.com/dept".equals(uri)) {
currentDeptId = attributes.getValue("id");
System.out.println("部门ID:" + currentDeptId);
}
// 处理部门名称节点
if ("name".equals(localName) && !"http://ipipp.com/dept".equals(uri) && currentDeptId != null && currentEmpId == null) {
isDeptName = true;
}
// 处理employee节点
if ("employee".equals(localName)) {
currentEmpId = attributes.getValue("id");
System.out.println(" 员工ID:" + currentEmpId);
}
// 处理员工姓名节点
if ("name".equals(localName) && currentEmpId != null) {
isEmpName = true;
}
// 处理员工年龄节点
if ("age".equals(localName)) {
isEmpAge = true;
}
// 处理技能节点
if ("skill".equals(localName)) {
isSkill = true;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String content = new String(ch, start, length).trim();
if (content.isEmpty()) {
return;
}
if (isDeptName) {
System.out.println("部门名称:" + content);
isDeptName = false;
}
if (isEmpName) {
System.out.println(" 员工姓名:" + content);
isEmpName = false;
}
if (isEmpAge) {
System.out.println(" 员工年龄:" + content);
isEmpAge = false;
}
if (isSkill) {
System.out.print(" 技能:" + content + " ");
isSkill = false;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("employee".equals(localName)) {
currentEmpId = null;
System.out.println();
}
if ("department".equals(localName) && "http://ipipp.com/dept".equals(uri)) {
currentDeptId = null;
}
}
};
parser.parse(new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8)), handler);
}
}
解析复杂XML的注意事项
解析复杂XML时需要注意以下几点,避免出现解析错误:
- 如果XML包含命名空间,解析时必须开启命名空间支持,否则无法正确匹配带前缀的节点
- 处理节点属性时,要注意属性可能不存在的情况,避免空指针异常
- 使用SAX解析时,characters方法可能会被多次调用,需要拼接内容后再处理
- 对于超大XML文件,优先选择SAX或者StAX解析,避免内存溢出
- 解析前先校验XML格式是否正确,避免解析过程中抛出格式错误异常
解析复杂XML的核心是先梳理清楚XML的节点结构、属性分布和命名空间情况,再根据文件大小和访问需求选择合适的解析方式,结合对应的API提取所需数据即可。