@@ -965,7 +965,7 @@ std::string s = u8"你好";
965
965
- 应用场景:常见于文字处理需求不大,但有强烈的跨平台需求,特别是互联网方面的软件。他们通常只用到字符串的拼接、查找、切片通常也只是在固定的位置(例如文件分隔符 ` '/' ` )。也非常适合主要面对的是以 ASCII 为主的“代码”类文本,UTF-8 是对英文类文本压缩率最高的,所以也广泛用于编译器、数据库之类的场景。同时因为 UTF-8 完全兼容 ASCII,使得他能轻易适配远古的 C 语言程序和库。
966
966
- 方法:始终以 UTF-8 编码存储和处理字符串。
967
967
- 优点:跨平台,在网络传输时无需任何转码,UTF-8 是互联网的主流编码格式,不同平台上运行的 UTF-8 软件可以随意共享文本数据。兼容 ASCII,方便复用现有库和生态。对英文类文本压缩率高,对中文文本也不算太差。
968
- - 缺点:对于底层 API 均采用 UTF-16 的 Windows 系统,需要进行字符编码转换,有少量性能损失。且字符串的正确切片、求长度等操作的复杂度会变成 $$$ O(N) $$$ 而不是通常的 $$ O(1) $ $ 。
968
+ - 缺点:对于底层 API 均采用 UTF-16 的 Windows 系统,需要进行字符编码转换,有少量性能损失。且字符串的正确切片、求长度等操作的复杂度会变成 $O(N)$ 而不是通常的 $O(1)$。
969
969
- 代表作:Rust 语言、Go 语言、CMake 构建系统、Julia 语言等。
970
970
971
971
在 C++ 中,可以通过 ` u8"你好" ` 创建一个保证内部是 UTF-8 编码的字符串常量,类型为 ` char8_t [] ` 。
@@ -1053,7 +1053,7 @@ Rust 和 Go:严格区分“字符 (32 位)”和“字节 (8 位)”的概念
1053
1053
| Go| ` rune ` | ` byte ` |
1054
1054
| Julia| ` Char ` | ` UInt8 ` |
1055
1055
1056
- 为此,这些语言都为字符串提供了两套 API,一种是按字符索引,一种是按字节索引。按字符索引时,会从头开始,逐个解析码位,直到解析到想要的字符为止,复杂度 $$$ O(N) $$$ 。按字节索引时,直接跳到指定字节,无需解析,复杂度 $$ O(1) $ $ 。
1056
+ 为此,这些语言都为字符串提供了两套 API,一种是按字符索引,一种是按字节索引。按字符索引时,会从头开始,逐个解析码位,直到解析到想要的字符为止,复杂度 $O(N)$。按字节索引时,直接跳到指定字节,无需解析,复杂度 $O(1)$。
1057
1057
1058
1058
``` rust
1059
1059
let s = " 你好" ;
@@ -1154,7 +1154,7 @@ for (char32_t c : Utf8Range(s)) {
1154
1154
- 应用场景:通常认为,UTF-16 是纯粹的历史遗留糟粕,新软件不应该再使用 UTF-16。只有在和这些糟粕软件的 API 打交道时,才必须转换为 UTF-16。但也有人指出:UTF-16 是纯中文压缩率最高的编码格式,所以 UTF-16 还比较适合纯中文或以中文内容为主的文本数据压缩。
1155
1155
- 方法:始终以 UTF-16 编码存储和处理字符串。
1156
1156
- 优点:调用 Windows 系统 API 时无需任何转换,直接就能调用,最适合 Windows 本地开发,非跨平台。且对纯中文内容可比 UTF-8 额外节省 33% 空间。
1157
- - 缺点:对于 Windows 以外的系统就需要转换回 UTF-8,有少量性能开销。且如果存储的内容主要是纯英文,如 XML 代码等,内存占用会比 UTF-8 翻倍。而且 UTF-16 仍然是变长编码,虽然出现变长的概率较低,但不为 0,仍需要开发者做特殊处理。字符串的按码位反转会导致生僻字符出错,字符串以码点为单位的的正确切片、求长度等操作的复杂度仍然 $$$ O(N)$$$ 而不是通常的 $$ O(1)$ $。并且 UTF-16 有大小端转换的问题。
1157
+ - 缺点:对于 Windows 以外的系统就需要转换回 UTF-8,有少量性能开销。且如果存储的内容主要是纯英文,如 XML 代码等,内存占用会比 UTF-8 翻倍。而且 UTF-16 仍然是变长编码,虽然出现变长的概率较低,但不为 0,仍需要开发者做特殊处理。字符串的按码位反转会导致生僻字符出错,字符串以码点为单位的的正确切片、求长度等操作的复杂度仍然 $O(N)$ 而不是通常的 $O(1)$。并且 UTF-16 有大小端转换的问题。
1158
1158
- 代表作:Windows 系统 API、Java 语言、Windows 文件系统 (NTFS)、Qt、Word、JSON,他们都是 UTF-16 的受害者。
1159
1159
1160
1160
这相当于是把 UTF-16 当作了内码,但 UTF-16 依然是一种变长编码,对常见的中文处理没问题,生僻字就容易出问题,且因为出现概率低,很容易不发现,埋下隐患。
@@ -1217,7 +1217,7 @@ fmt::println("{}", s.size()); // 3
1217
1217
1218
1218
- 应用场景:适合需要经常处理文字的领域,如文本编辑器、浏览器等。但不适合存储和传输,因为浪费硬盘和网络带宽。字符串一般都长期以 UTF-8 存储,只有在需要频繁索引码位时,才需要转换为 UTF-32。
1219
1219
- 方法:始终以 UTF-32 编码存储和处理字符串。
1220
- - 优点:字符串的按码位反转、切片、求长度等操作都是 $$$ O(1)$$ $ 的复杂度,可以当作普通数组一样,随意处理。例如你可以设想一个文本编辑框,需要支持“退格”操作,如果是 UTF-8 和 UTF-16 就需要繁琐的判断代理对、各种车厢,而 UTF-32 的字符串只需要一次 `pop_back` 就搞定了。
1220
+ - 优点:字符串的按码位反转、切片、求长度等操作都是 $O(1)$ 的复杂度,可以当作普通数组一样,随意处理。例如你可以设想一个文本编辑框,需要支持“退格”操作,如果是 UTF-8 和 UTF-16 就需要繁琐的判断代理对、各种车厢,而 UTF-32 的字符串只需要一次 `pop_back` 就搞定了。
1221
1221
- 缺点:浪费空间大,通常在保存时,仍然需要转换回 UTF-8 后再写入文件,有一定性能开销。
1222
1222
1223
1223
*总结:要支持 UTF-32 阵营,请全部使用 `char32_t` 和 `std::u32string`。字面量全用 `U"你好"` 的形式书写,读文件时转为 UTF-32,写文件时转回 UTF-8。*
@@ -1827,7 +1827,7 @@ TODO
1827
1827
//
1828
1828
//缺点是这样的软件会无法跨平台,因为 `wchar_t` 在 Linux 上是安全的内码 UTF-32。而 Windows 上是 UTF-16,是不定长的编码,如果存在“𰻞”和“😉”这样超过 0x10000 的生僻字,就会产生两个 `wchar_t`!如果文字处理涉及切片,就会出问题。概率很低,但不为零,软件仍然需要对可能存在的双 `wchar_t` 做特殊处理。若不处理,轻则乱码,重则留下漏洞,被黑客攻击,加重了 Windows 和 Java 程序员的心智负担。
1829
1829
//
1830
- //如果一个程序(例如 GCC)只适配了 `wchar_t` 是 UTF-32 的平台,想当然的把 `wchar_t` 当作安全的定长内码使用,那移植到 Windows 上后就会丧失处理“𰻞”和“😉”的能力。要么就需要对所有代码大改,把原本 $$$ O(1)$$$ 的字符串求长度改成 $$ O(N)$ $ 的;要么出现乱码,被黑客攻击。
1830
+ //如果一个程序(例如 GCC)只适配了 `wchar_t` 是 UTF-32 的平台,想当然的把 `wchar_t` 当作安全的定长内码使用,那移植到 Windows 上后就会丧失处理“𰻞”和“😉”的能力。要么就需要对所有代码大改,把原本 $O(1)$ 的字符串求长度改成 $O(N)$ 的;要么出现乱码,被黑客攻击。
1831
1831
//
1832
1832
//当需要读写二进制文件时,使用 `fstream`,原封不动地按“字节”为单位读取。
1833
1833
//
@@ -2297,7 +2297,7 @@ stream << "你好,世界\n"; // 写入 QString,QTextStream 会自动将其
2297
2297
2298
2298
如果用 UTF-8 或 UTF-16 来存储的话,会遇到变长编码的固有缺陷:
2299
2299
2300
- 例如像字符串索引,字符串求长度等操作,要么索引出来的是字节而不是字符了;要么就需要 $$$ O(N)$$ $ 的复杂度,逐一遍历每个字节,才能确定真正的位置;哪怕全是 ASCII 也得这么做,因为万一刚好有一个是中文字符呢?
2300
+ 例如像字符串索引,字符串求长度等操作,要么索引出来的是字节而不是字符了;要么就需要 $O(N)$ 的复杂度,逐一遍历每个字节,才能确定真正的位置;哪怕全是 ASCII 也得这么做,因为万一刚好有一个是中文字符呢?
2301
2301
2302
2302
所以,对于经常需要处理字符串的 Python 来说,UTF-8 是无法接受的,似乎只能以 UTF-32 来存储?
2303
2303
@@ -2344,7 +2344,7 @@ struct PyUnicodeString {
2344
2344
2345
2345
### Rust ` &str ` 和 ` String `
2346
2346
2347
- 而 Rust 则采用了字符串全员 UTF-8 的策略,这是因为 Rust 最常用于互联网方面的底层系统软件,互联网最常用的文本编码就是 UTF-8,没有大小端问题,且国际通用。除此之外,互联网基建最常见的平台就是 Linux,使用 UTF-8 存储字符串,调用 Linux 系统 API 无需任何转换。且文本文件基本都可以假定是 UTF-8 编码,写入时无需任何转换,复杂度低至 $$$ O(1) $$$ 。作为代价,这导致文本处理上的一些困难,例如字符串的索引,需要区分是按字节索引还是按字符索引,如果确实需要按字符索引的话,复杂度就会是 $$ O(N) $ $ 了。
2347
+ 而 Rust 则采用了字符串全员 UTF-8 的策略,这是因为 Rust 最常用于互联网方面的底层系统软件,互联网最常用的文本编码就是 UTF-8,没有大小端问题,且国际通用。除此之外,互联网基建最常见的平台就是 Linux,使用 UTF-8 存储字符串,调用 Linux 系统 API 无需任何转换。且文本文件基本都可以假定是 UTF-8 编码,写入时无需任何转换,复杂度低至 $O(1)$。作为代价,这导致文本处理上的一些困难,例如字符串的索引,需要区分是按字节索引还是按字符索引,如果确实需要按字符索引的话,复杂度就会是 $O(N)$ 了。
2348
2348
2349
2349
无论如何,如果你选择了 UTF-8 流派的话,Rust 字符串的“迭代器双轨制”确实值得称道:
2350
2350
0 commit comments