英文: https://www.objc.io/issues/9-strings/unicode/
https://learn.microsoft.com/en-us/dotnet/api/system.string.normalize?view=net-8.0
当你在处理文本时,如果你不是在写一些非常古老的代码(legacy code),那么你一定要使用 Unicode。幸运的是,苹果和 NeXT 一直致力于推动 Unicode 标准的建立,而 NeXT 在 1994 年推出的 Foundation Kit 则是所有编程语言中最先基于 Unicode 的标准库之一。但是,即使 NSString
完全支持 Unicode,还替你干了大部分的重活儿,处理各种语言、各种书写系统的文本仍然是一个非常复杂的事情。作为一个程序员,有些事情你应该知道。
这篇文章里,我会先向你简单地讲一下 Unicode 这个标准,然后解释 NSString
是怎么处理它的,再讨论一下你可能会遇到的一些常见问题。
历史
计算机没法直接处理文本,它只和数字打交道。为了在计算机里用数字表示文本,我们指定了一个从字符到数字的映射。这个映射就叫做编码(encoding)。
最有名的一个字符编码是 ASCII。ASCII 码是 7 位的,它将英文字母,数字 0-9 以及一些标点符号和控制字符映射为 0-127 这些整型。随后,人们创造了许多不同的 8 位编码来处理英语以外的其他语言。它们大多都是基于 ASCII 编码的,并且使用了 ASCII 没有使用的第 8 位来编入其它字母、符号甚至是整个字母表(比如西里尔字母和希腊字母)。
当然,这些编码系统相互之间并不兼容,并且,由于 8 位的空间对于欧洲的文字来说都不够,更不用说全世界的书写系统了,因此这种不兼容是肯定会出现的了。这对于当时基于文本的操作系统来说是很麻烦的,因为那时操作系统只能同时使用一种编码(也叫做内码表,code page)。如果你在一台机器上写了一段文字,然后在另一台使用了不同的内码表的机器上打开,那么在 128-255 这个范围内的字符就会显示错误。
诸如中文、日文和韩文的东亚文字又让情况更加复杂。这些书写系统里包含的字符实在是太多了,以至于 8 位的数字所能提供的 256 个位置远远不够。结果呢,人们开发了更加通用的编码(通常是 16 位的)。当你开始纠结于如何处理一个字节装不下的值时,如何把它存储到内存或者硬盘里就变得十分关键了。这时,就必须再进行第二次映射,以此来确定字节的顺序。而且,最好使用可变长度的编码而不是固定长度的。请注意,第二次映射其实是另一种形式的“编码”。我们把这两个映射都叫做“编码”很容易造成误解。这个在下面 UTF-8 和 UTF-16 的部分里会再作讨论。
现代操作系统都已经不再局限于只能同时使用一种内码表了,因此只要每个文档都清楚地标明自己使用的是哪种编码,处理几十甚至上百种编码系统尽管很讨厌,也完全是有可能的。真正不可能的是在一个文档里_混合_使用多种编码系统,因此撰写多语言的文档也不可能了,而正是这一点终结了在 Unicode 编码出现之前,多种编码混战的局面。
1987 年,来自几个大的科技公司(其中包括苹果和 NeXT)的工程师们开始合作致力于开发一种能在全世界所有书写系统中通用的字符编码系统,于 1991 年 10 月发布的 1.0.0 版本的 Unicode 标准就是这一努力的成果。
Unicode 概要
基本介绍
简单地来说,Unicode 标准为世界上几乎所有的[^1]书写系统里所使用的每一个字符或符号定义了一个唯一的数字。这个数字叫做码点(code points),以 U+xxxx
这样的格式写成,格式里的 xxxx
代表四到六个十六进制的数。比如,U+0041(十进制是 65)这个码点代表拉丁字母表(和 ASCII 一致)里的字母 A;U+1F61B 代表名为“伸出舌头的脸”的 emoji,也就是