1. 概述
本文将介绍在 Kotlin 中如何将字符串以 UTF-8 编码。
编码本质上是用字节序列来表示字符的方式。UTF-8 是最常见、也通常是默认的文本编码格式,广泛应用于数据传输和存储场景,推荐作为首选编码方式。Kotlin 支持多种编码类型,统称为 Charsets,你可以在官方文档查看完整列表:Charsets 文档。
我们先通过一个例子理解为什么需要使用 UTF-8,它与其他编码有何区别,然后演示几种确保使用 UTF-8 编码的方法。
⚠️ 注意:以下示例中假设输入的编码是任意的(即使很多时候默认就是 UTF-8),目的是模拟真实开发中处理未知编码数据的场景。
2. 不同编码之间的差异
为了更清楚地说明问题,来看下面这段代码,对比 UTF-8 与 ASCII 编码的行为:
val originalString = "That will cost €10."
val stringAsByteArray = originalString.toByteArray()
val utf8String = String(stringAsByteArray, Charsets.UTF_8)
val asciiString = String(stringAsByteArray, Charsets.US_ASCII)
Assertions.assertEquals(originalString, utf8String)
Assertions.assertNotEquals(originalString, asciiString)
Assertions.assertEquals("That will cost 10.", asciiString)
✅ 结果分析:
- UTF-8 能正确还原原始字符串;
- ASCII 无法识别
€
符号,导致乱码(显示为 ``)。
❌ 原因:ASCII 只支持基本英文字符(0–127),而 €
属于 Unicode 字符,必须由支持扩展字符集的编码(如 UTF-8)才能正确表示。
📌 关键知识点:
- UTF-8 是变长编码,每个字符至少占用 1 字节(8 bits),名字由此而来。
- 相比之下,UTF-16 至少占 2 字节,UTF-32 固定占 4 字节。
- ✅ UTF-8 兼容 ASCII:所有 ASCII 字符在 UTF-8 中的编码完全一致,这意味着老系统通常也能安全处理含 UTF-8 扩展字符的文件。
正是由于其高效性(节省内存)和兼容性,UTF-8 成为了现代应用的事实标准。
3. 测试环境准备
为了统一验证后续方法的效果,我们预先定义一组测试数据。实际项目中,这些数据可能来自网络请求或文件读取等外部源。
val byteArray = byteArrayOf(84, 104, 97, 116, 32, 119, 105, 108, 108, 32, 99, 111, 115, 116, 32, -30, -126, -84, 49, 48, 46)
val charArray = charArrayOf('T', 'h', 'a', 't', ' ', 'w', 'i', 'l', 'l', ' ', 'c', 'o', 's', 't', ' ', '€', '1', '0', '.')
val expectedString = "That will cost €10."
🔍 说明:
byteArray
中-30, -126, -84
是€
在 UTF-8 下的三字节表示(对应十六进制0xE2 0x82 0xAC
)。负数是因为 Java/Kotlin 的Byte
是有符号的。
你可以根据需要替换为其他测试值。
4. 从 ByteArray 转换为 UTF-8 字符串
当已有字节数组时,有多种方式将其解码为 UTF-8 字符串。
4.1 使用 String 构造函数
方式一:使用默认构造函数(推荐)
val utf8String = String(byteArray)
Assertions.assertEquals(expectedString, utf8String)
✅ 原理:String(byteArray)
默认使用平台默认编码(通常是 UTF-8),但在多数 JVM 环境下,尤其是现代 Linux/macOS 系统,默认编码确实是 UTF-8。
⚠️ 踩坑提示:不要依赖默认行为跨平台!Windows 某些区域设置下默认编码可能是 GBK
或 Cp1252
,会导致乱码。生产环境建议显式指定编码。
方式二:显式指定 Charset
val utf8String = String(byteArray, Charsets.UTF_8)
Assertions.assertEquals(expectedString, utf8String)
✅ 推荐做法:明确指定 Charsets.UTF_8
,避免平台差异带来的风险。
4.2 使用 ByteArray.toString(Charset) 扩展函数
Kotlin 提供了 ByteArray
的扩展函数 toString()
,支持传入 Charset
参数:
val utf8StringDefault = byteArray.toString()
val utf8StringExplicit = byteArray.toString(Charsets.UTF_8)
Assertions.assertNotEquals(expectedString, utf8StringDefault)
Assertions.assertEquals(expectedString, utf8StringExplicit)
⚠️ 注意:
- ❌
byteArray.toString()
不会返回字符串内容,而是返回类似[B@7a81197d
的对象地址(即数组的 toString() 实现)。 - ✅ 必须使用重载版本
toString(Charset)
才能正确解码。
📌 正确用法示例:
val text = bytes.toString(Charsets.UTF_8)
5. 从 CharArray 转换为 UTF-8 字符串
CharArray
本身不携带编码信息,直接转成 String
并不能保证输出为 UTF-8 编码的字节流。要确保最终结果是以 UTF-8 编码的字符串,需经过“编码 → 解码”流程。
5.1 使用 Charset.encode() 和 decode()
核心思路:
- 将
CharArray
包装为CharBuffer
- 使用
Charset.encode()
转为 UTF-8 编码的ByteBuffer
- 再用
Charset.decode()
将ByteBuffer
解码回CharBuffer
- 最终转为
String
实现如下:
val encodedByteBuffer = Charsets.UTF_8.encode(CharBuffer.wrap(charArray))
val utf8String = Charsets.UTF_8.decode(encodedByteBuffer).toString()
Assertions.assertEquals(expectedString, utf8String)
✅ 优势:
- 显式控制编码过程,确保中间字节流为 UTF-8;
- 避免原始
charArray
可能存在的隐式编码污染。
⚠️ 踩坑提醒:
- 直接使用
String(charArray)
虽然语法简单,但不涉及编码转换,只是构建字符串对象,无法保证底层字节为 UTF-8。 - 如果你要将字符串写入文件或发送 HTTP 请求,真正起作用的是
.toByteArray(Charsets.UTF_8)
这一步。
📌 实际应用场景举例:
// 正确:确保输出为 UTF-8 字节流
val utf8Bytes: ByteArray = myString.toByteArray(Charsets.UTF_8)
File("output.txt").writeBytes(utf8Bytes)
6. 总结
本文重点梳理了 Kotlin 中处理 UTF-8 编码的关键实践:
✅ 核心要点总结:
场景 | 推荐做法 |
---|---|
ByteArray → String |
使用 String(bytes, Charsets.UTF_8) |
获取 UTF-8 字节数组 | 使用 "str".toByteArray(Charsets.UTF_8) |
CharArray → UTF-8 String |
借助 Charset.encode/decode 流程确保编码正确 |
避免踩坑 | 永远不要依赖默认编码,尤其是在跨平台或国际化场景 |
📌 最佳实践建议:
- 所有 I/O 操作(文件、网络、数据库)都应显式声明编码;
- 日志、配置文件、API 接口统一使用 UTF-8;
- 单元测试中加入非 ASCII 字符(如
€
,中文
,한국어
)验证编码逻辑。
示例代码已上传至 GitHub:Baeldung Kotlin 教程仓库
保持编码一致性,远离乱码烦恼 💯