Loading... ## 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 字节) ### 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 字节) ### UTF-32 UTF-32 采用 **固定 4 字节**,直接存储 Unicode 码点。 - `A` (U+0041) → `0x00000041` - "你" (U+4F60) → `0x00004F60` - "😊" (U+1F60A) → `0x0001F60A` 虽然 UTF-32 访问字符时更简单,但它的存储效率低,因此很少用于文本处理。 ## 为什么 String.length 可能返回不符合预期的值? 大部分编程语言的 `String.length` **不会计算人类可感知的字符数**,而是计算**编码单元的数量**,这导致如下问题: ```dart void main() { String text = "😊"; print(text.length); // Dart: 2(UTF-16 编码,存储为两个 16 位单元) } ``` 类似地,在 JavaScript、Java、Kotlin 等 UTF-16 语言中,一个单个 Emoji 可能会被 `length` 统计为 2。 ```javascript console.log("😊".length); // JavaScript: 2 ``` ## 为什么 `substring` 可能会导致问题? 假如我们对包含 Emoji 的字符串使用 `substring`,但起始或结束索引正好落在 `Surrogate Pair` 之间,就可能截断字符,导致乱码或异常: ### JavaScript 示例 ```javascript let text = "Hello 😊!"; console.log(text.substring(0, 7)); // "Hello ?"(乱码) ``` ### Dart 示例 ```dart void main() { String text = "Hello 😊!"; print(text.substring(0, 7)); // 可能崩溃或显示乱码 } ``` **解决方案**:可以使用 `runes`(Unicode 码点遍历)来正确处理字符: ```dart void main() { String text = "Hello 😊!"; print(String.fromCharCodes(text.runes.take(7))); // "Hello 😊" } ``` ## 哪些语言避免了这个问题? ### Swift 的 `String` 设计 Swift 使用 **`Character` 作为 Unicode 标准化的感知单元**,可以正确处理 Emoji 和组合字符。 ```swift let text = "Hello 😊!" print(text.count) // 8(正确) ``` Swift 也保证 `String` 操作不会截断 `Grapheme Cluster`(如 Emoji 组合): ```swift let index = text.index(text.startIndex, offsetBy: 7) print(text[..<index]) // "Hello 😊" ``` ## 结论 1. **`length` 不可靠**:许多语言(Dart、JavaScript、Kotlin)会把 Emoji 视为 2 或 3 个字符,而不是 1。 2. **`substring` 可能截断字符**,导致乱码或异常。 3. **解决方案**: - 遍历 `runes` 或 `codePoints` 以正确处理 Unicode 码点。 - 使用 Swift 这样的语言,其 `String` API 内部保证不会截断 `Grapheme Cluster`。 Emoji 在 Unicode 中的存储方式使得很多编程语言需要特殊处理,否则可能会导致文本显示错误或逻辑异常。理解 Unicode 及其不同的编码方式,有助于开发者在各种语言中正确处理多字符单元的文本。 © 允许规范转载 打赏 赞赏作者 微信 赞