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 某些区域设置下默认编码可能是 GBKCp1252,会导致乱码。生产环境建议显式指定编码。

方式二:显式指定 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()

核心思路:

  1. CharArray 包装为 CharBuffer
  2. 使用 Charset.encode() 转为 UTF-8 编码的 ByteBuffer
  3. 再用 Charset.decode()ByteBuffer 解码回 CharBuffer
  4. 最终转为 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 编码的关键实践:

✅ 核心要点总结:

场景 推荐做法
ByteArrayString 使用 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 教程仓库

保持编码一致性,远离乱码烦恼 💯


原始标题:How to Encode a String in UTF-8 in Kotlin