导读:本期聚焦于相泽南创作的《Kotlin中如何方便地解析XML?有哪些简便的实现方法》,敬请观看详情。很多开发者在使用Kotlin开发Android应用或者后端服务时,都会遇到需要解析XML格式数据的场景。传统的XML解析方式往往代码繁琐,步骤复杂,容易出错。本文结合实际开发需求,介绍几种在Kotlin中解析XML的简便方法,包括基于标准库的XmlPullParser解析、DOM解析、SAX解析以及第三方库的轻量解析方案。每种方法都会给出完整的代码示例和适用场景说明,帮助开发者根据实际的XML数据规模、解析需求选择最合适的实现方式,减少冗余代码,提升开发效率。

Kotlin 解析 XML 的典型场景与选型思路

在 Kotlin 项目中,XML 仍然经常出现在配置文件、接口响应、模板数据、旧系统交换格式等位置。无论是 Android 应用读取本地配置,还是服务端程序处理遗留接口,都会遇到把 XML 文本转换为对象或结构化数据的需求。解析方式的选择,会直接影响内存占用、代码复杂度以及后续维护成本。

常见需求可以分成几类。第一类是读取本地配置,这类文档通常较小,更看重代码直观程度。第二类是处理网络响应,服务端可能返回 XML 格式数据,需要快速转换成业务模型。第三类是对接第三方系统,文档结构可能较复杂,甚至包含命名空间、属性节点和嵌套列表。不同需求下,最适合的解析器并不相同。

  • 小型文档优先考虑可读性和开发效率。
  • 大型文档优先考虑低内存和顺序解析能力。
  • 模型结构稳定时,优先考虑对象映射。
  • Android 平台可以优先考虑系统熟悉的解析接口。

从技术路线看,Kotlin 可以直接复用 Java 生态中的 XML 处理能力,也可以使用 Android 平台提供的解析接口。常见方案包括拉式解析、文档对象模型解析、事件驱动解析,以及基于第三方库的对象映射。理解这些方案的差异,有助于在实际项目中做出更合适的取舍。

使用 XmlPullParser 实现低内存拉式解析

XmlPullParser 是一种拉式解析方式。它不会把整个文档一次性装入内存,而是由开发者主动驱动解析器向后移动。解析器在前进过程中会不断报告当前事件,例如开始文档、开始标签、文本内容、结束标签和结束文档。开发者只需要在关键事件上提取数据即可。

在 Android 开发场景中,XmlPullParser 是比较常见的选择。它本身占用资源较少,也适合处理体积较大的 XML 数据。相比完全手写字符串截取,它能够正确处理标签嵌套、文本内容和结束边界,可靠性更高。相比事件驱动解析,它的代码流程又更接近顺序阅读,状态管理相对容易理解。

下面的示例把一段包含多个 <user> 节点的 XML 字符串解析为 List<User> 结果。示例使用 data class 保存结果,并在遇到结束标签时把临时字段组装成完整对象。

import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserFactory
import java.io.StringReader

// 定义用户数据模型
data class User(
    val id: Int,
    val name: String,
    val age: Int
)

// 使用 XmlPullParser 顺序读取节点
fun parseUsersWithPullParser(xml: String): List<User> {
    val factory = XmlPullParserFactory.newInstance()
    val parser = factory.newPullParser()
    parser.setInput(StringReader(xml))

    val users = mutableListOf<User>()
    var eventType = parser.eventType

    var id = 0
    var name = ""
    var age = 0

    while (eventType != XmlPullParser.END_DOCUMENT) {
        when (eventType) {
            XmlPullParser.START_TAG -> {
                when (parser.name) {
                    "id" -> id = parser.nextText().toIntOrNull() ?: 0
                    "name" -> name = parser.nextText()
                    "age" -> age = parser.nextText().toIntOrNull() ?: 0
                }
            }
            XmlPullParser.END_TAG -> {
                if (parser.name == "user") {
                    users.add(User(id, name, age))
                    id = 0
                    name = ""
                    age = 0
                }
            }
        }
        eventType = parser.next()
    }

    return users
}

fun main() {
    val xml = """
        <users>
            <user>
                <id>1</id>
                <name>张三</name>
                <age>28</age>
            </user>
            <user>
                <id>2</id>
                <name>李四</name>
                <age>32</age>
            </user>
        </users>
    """.trimIndent()

    println(parseUsersWithPullParser(xml))
}

这个示例中,nextText 用于读取当前元素的文本内容,eventType 用于判断解析器当前所处的位置。为了减少空值或非法数字带来的异常,示例使用 toIntOrNull 做安全转换。实际项目中,如果 XML 来自文件或网络,也可以把 StringReader 替换成 InputStream,并保持相同的事件处理逻辑。

使用 DOM 与 SAX 处理结构化文档

DOM 解析会把整个 XML 文档读取到内存中,并构建成一棵节点树。它的优势是结构清晰,开发者可以通过元素名快速查找节点,也可以在文档中反复遍历。对于体积较小、结构较稳定的 XML 文件,DOM 往往是最容易编写和维护的方案。

在 Kotlin 中,DOM 解析通常借助 Java 标准库中的 DocumentBuilderFactory 完成。先创建 DocumentBuilder,再把输入源交给解析器,最后得到 Document 对象。得到文档树之后,可以使用 getElementsByTagName 查找节点,再读取其中的文本内容。

import javax.xml.parsers.DocumentBuilderFactory
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import org.xml.sax.InputSource
import java.io.StringReader

data class User(
    val id: Int,
    val name: String,
    val age: Int
)

// 使用 DOM 将 XML 文档加载为树结构
fun parseUsersWithDom(xml: String): List<User> {
    val factory = DocumentBuilderFactory.newInstance()
    val builder = factory.newDocumentBuilder()
    val document: Document = builder.parse(InputSource(StringReader(xml)))
    document.documentElement.normalize()

    val users = mutableListOf<User>()
    val userNodes: NodeList = document.getElementsByTagName("user")

    for (index in 0 until userNodes.length) {
        val node = userNodes.item(index)
        if (node.nodeType != Node.ELEMENT_NODE) {
            continue
        }

        val element = node as Element
        val id = element.getElementsByTagName("id").item(0)?.textContent?.toIntOrNull() ?: 0
        val name = element.getElementsByTagName("name").item(0)?.textContent.orEmpty()
        val age = element.getElementsByTagName("age").item(0)?.textContent?.toIntOrNull() ?: 0

        users.add(User(id, name, age))
    }

    return users
}

fun main() {
    val xml = """
        <users>
            <user>
                <id>1</id>
                <name>张三</name>
                <age>28</age>
            </user>
            <user>
                <id>2</id>
                <name>李四</name>
                <age>32</age>
            </user>
        </users>
    """.trimIndent()

    println(parseUsersWithDom(xml))
}

SAX 解析与 DOM 的思路不同。SAX 不会在内存中保存完整文档,而是在读取过程中触发事件。开发者需要继承 DefaultHandler,并在开始元素、文本内容和结束元素等回调中维护自己的状态。由于它不需要构建整棵树,所以适合处理大型 XML 文件。

SAX 的代价是代码复杂度更高。开发者必须清楚当前位于哪个节点,也要在合适的时机重置临时状态。如果 XML 结构较深,或者需要同时处理属性、命名空间和列表包装节点,处理器中的状态判断会变得更复杂。因此,SAX 更适合只读取一次、不需要随机访问节点的场景。

import javax.xml.parsers.SAXParserFactory
import org.xml.sax.Attributes
import org.xml.sax.InputSource
import org.xml.sax.helpers.DefaultHandler
import java.io.StringReader

data class User(
    val id: Int,
    val name: String,
    val age: Int
)

// 自定义 SAX 处理器,按事件收集节点内容
class UserSaxHandler : DefaultHandler() {
    private val users = mutableListOf<User>()
    private val buffer = StringBuilder()

    private var id = 0
    private var name = ""
    private var age = 0

    override fun startElement(
        uri: String?,
        localName: String?,
        qName: String?,
        attributes: Attributes?
    ) {
        buffer.setLength(0)
    }

    override fun characters(ch: CharArray?, start: Int, length: Int) {
        if (ch == null) {
            return
        }
        buffer.append(ch, start, length)
    }

    override fun endElement(uri: String?, localName: String?, qName: String?) {
        val content = buffer.toString().trim()

        when (qName) {
            "id" -> id = content.toIntOrNull() ?: 0
            "name" -> name = content
            "age" -> age = content.toIntOrNull() ?: 0
            "user" -> {
                users.add(User(id, name, age))
                id = 0
                name = ""
                age = 0
            }
        }

        buffer.setLength(0)
    }

    fun getResult(): List<User> = users
}

fun parseUsersWithSax(xml: String): List<User> {
    val factory = SAXParserFactory.newInstance()
    val parser = factory.newSAXParser()
    val handler = UserSaxHandler()

    parser.parse(InputSource(StringReader(xml)), handler)
    return handler.getResult()
}

fun main() {
    val xml = """
        <users>
            <user>
                <id>1</id>
                <name>张三</name>
                <age>28</age>
            </user>
            <user>
                <id>2</id>
                <name>李四</name>
                <age>32</age>
            </user>
        </users>
    """.trimIndent()

    println(parseUsersWithSax(xml))
}

使用第三方库完成对象映射

当 XML 结构和业务模型比较稳定时,手写解析逻辑会显得重复。此时可以考虑第三方映射库,例如 Jackson XMLSimple XML。这类库通常通过注解描述 XML 节点和 Kotlin 类之间的对应关系,然后把解析过程交给框架完成。

Jackson XML 为例,开发者可以使用 @JacksonXmlRootElement 描述根节点,使用 @JacksonXmlProperty 描述字段对应的节点名,使用 @JacksonXmlElementWrapper 处理列表包装关系。完成映射配置后,只需要调用 XmlMapperreadValue 方法,就可以把 XML 字符串转换成对象。

import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement

// 使用前需要在构建脚本中加入 Jackson XML 相关依赖
@JacksonXmlRootElement(localName = "users")
data class Users(
    @JacksonXmlElementWrapper(useWrapping = false)
    @JacksonXmlProperty(localName = "user")
    val user: List<User>? = null
)

data class User(
    @JacksonXmlProperty(localName = "id")
    val id: Int = 0,
    @JacksonXmlProperty(localName = "name")
    val name: String = "",
    @JacksonXmlProperty(localName = "age")
    val age: Int = 0
)

// 使用 XmlMapper 将 XML 字符串映射为对象
fun parseUsersWithJackson(xml: String): List<User> {
    val mapper = XmlMapper()
    return mapper.readValue(xml, Users::class.java).user.orEmpty()
}

fun main() {
    val xml = """
        <users>
            <user>
                <id>1</id>
                <name>张三</name>
                <age>28</age>
            </user>
            <user>
                <id>2</id>
                <name>李四</name>
                <age>32</age>
            </user>
        </users>
    """.trimIndent()

    println(parseUsersWithJackson(xml))
}

第三方库的优势是代码量少,模型清晰,适合服务端项目或依赖体积不受严格限制的项目。不过,在 Android 等对包体积和启动速度敏感的环境中,需要评估依赖大小、反射开销和兼容性。对于简单配置解析,引入完整映射框架未必是最划算的选择。

解析方式内存占用编码复杂度适用场景
XmlPullParser中等Android 或顺序读取中大型 XML
DOM小型 XML,需要随机访问节点
SAX较高大型 XML,只按顺序消费数据
Jackson XML取决于映射过程对象映射,服务端或依赖允许的项目

从表中可以看出,没有一种方案适合所有场景。XmlPullParser 在内存和可读性之间比较平衡,DOM 更适合小文档,SAX 更适合大文档,第三方库更适合对象映射。选型时应优先明确文档大小、访问方式、运行平台和依赖限制。

总结与工程建议

在实际项目中,建议先判断 XML 的规模和访问模式。如果文档较小,并且需要频繁查询节点,DOM 很合适。如果文档较大,并且只能顺序消费,XmlPullParserSAX 更稳妥。如果项目运行在 Android 平台,优先使用平台熟悉的解析接口,可以减少额外依赖。

其次要考虑维护成本。对于一次性脚本或简单配置,代码越直观越好。对于长期维护的业务系统,最好把 XML 结构映射成明确的 Kotlin 数据模型,避免在多个模块中重复书写节点名和类型转换。使用 data class 承载结果,也能让后续测试和调试更容易。

最后需要注意健壮性。无论选择哪种解析方式,都应处理缺失节点、空文本、非法数字和编码异常。对于来自外部系统的数据,还应关注 XML 安全配置,避免不必要的外部实体解析。如今 XML 已不是唯一的数据交换格式,但在遗留系统和企业接口中仍然常见,掌握多种解析方式能够让 Kotlin 项目更从容地应对真实需求。

KotlinXML解析DOM解析SAX解析XmlPullParser修改时间:2026-07-11 05:03:13

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。