Unicode 基础
Unicode 是一种字符编码标准,旨在为全球所有书写系统中的字符分配唯一的编码点。每个字符都被分配了一个码点(Code Point),通常以 U+XXXX
的形式表示。例如:
- 英文字母 A:
U+0041
- 中文字符 "你":
U+4F60
- Emoji "😊":
U+1F60A
然而,Unicode 本身只是一个字符集,实际存储和处理时,需要转换为具体的编码格式,如 UTF-8、UTF-16、UTF-32。
UTF 编码
UTF-8
UTF-8 是一种变长编码,字符的长度可以是 1 到 4 个字节。
- ASCII 兼容(1 字节):
A
(U+0041) →0x41
多字节字符(2-4 字节):
- "你" (U+4F60) →
0xE4 0xBD 0xA0
(3 字节) - "😊" (U+1F60A) →
0xF0 0x9F 0x98 0x8A
(4 字节)
- "你" (U+4F60) →
UTF-16
UTF-16 采用 2 或 4 字节 进行编码。
基本多语言平面(BMP):2 字节
A
(U+0041) →0x0041
- "你" (U+4F60) →
0x4F60
超出 BMP(辅助平面,Surrogate Pairs):
- "😊" (U+1F60A) →
0xD83D 0xDE0A
(2 个 16 位单元,合计 4 字节)
- "😊" (U+1F60A) →
UTF-32
UTF-32 采用 固定 4 字节,直接存储 Unicode 码点。
A
(U+0041) →0x00000041
- "你" (U+4F60) →
0x00004F60
- "😊" (U+1F60A) →
0x0001F60A
虽然 UTF-32 访问字符时更简单,但它的存储效率低,因此很少用于文本处理。
为什么 String.length 可能返回不符合预期的值?
大部分编程语言的 String.length
不会计算人类可感知的字符数,而是计算编码单元的数量,这导致如下问题:
void main() {
String text = "😊";
print(text.length); // Dart: 2(UTF-16 编码,存储为两个 16 位单元)
}
类似地,在 JavaScript、Java、Kotlin 等 UTF-16 语言中,一个单个 Emoji 可能会被 length
统计为 2。
console.log("😊".length); // JavaScript: 2
为什么 substring
可能会导致问题?
假如我们对包含 Emoji 的字符串使用 substring
,但起始或结束索引正好落在 Surrogate Pair
之间,就可能截断字符,导致乱码或异常:
JavaScript 示例
let text = "Hello 😊!";
console.log(text.substring(0, 7)); // "Hello ?"(乱码)
Dart 示例
void main() {
String text = "Hello 😊!";
print(text.substring(0, 7)); // 可能崩溃或显示乱码
}
解决方案:可以使用 runes
(Unicode 码点遍历)来正确处理字符:
void main() {
String text = "Hello 😊!";
print(String.fromCharCodes(text.runes.take(7))); // "Hello 😊"
}
哪些语言避免了这个问题?
Swift 的 String
设计
Swift 使用 Character
作为 Unicode 标准化的感知单元,可以正确处理 Emoji 和组合字符。
let text = "Hello 😊!"
print(text.count) // 8(正确)
Swift 也保证 String
操作不会截断 Grapheme Cluster
(如 Emoji 组合):
let index = text.index(text.startIndex, offsetBy: 7)
print(text[..<index]) // "Hello 😊"
结论
length
不可靠:许多语言(Dart、JavaScript、Kotlin)会把 Emoji 视为 2 或 3 个字符,而不是 1。substring
可能截断字符,导致乱码或异常。解决方案:
- 遍历
runes
或codePoints
以正确处理 Unicode 码点。 - 使用 Swift 这样的语言,其
String
API 内部保证不会截断Grapheme Cluster
。
- 遍历
Emoji 在 Unicode 中的存储方式使得很多编程语言需要特殊处理,否则可能会导致文本显示错误或逻辑异常。理解 Unicode 及其不同的编码方式,有助于开发者在各种语言中正确处理多字符单元的文本。