1、字节和字符的区别
ASCII时代,字节和字符是一样的。当Unicode出现后,事情有所不同了。字节(octet)是一个八位的存储单元,取值范围一定是0~255。而字符(character,或者word)为语言意义上的符号,范围就不一定了。例如在UCS-2中定义的字符范围为0~65535,它的一个字符占用两个字节。
2、UCS-2和UCS-4
Unicode是为整合全世界的所有语言文字而诞生的。任何文字在Unicode中都对应一个值,这个值称为代码点(code point)。代码点的值通常写成U+ABCD的格式。而文字和代码点之间的对应关系就是UCS-2(Universal Character Set coded in 2 octets)。顾名思义,UCS-2是用两个字节来表示代码点,其取值范围为 U+0000~U+FFFF。
为了能表示更多的文字,人们又提出了UCS-4,即用四个字节表示代码点。它的范围为 U+00000000~U+7FFFFFFF,其中 U+00000000~U+0000FFFF和UCS-2是一样的。
要注意,UCS-2和UCS-4只规定了代码点和文字之间的对应关系,并没有规定代码点在计算机中如何存储。规定存储方式的称为UTF(Unicode Transformation Format),其中应用较多的就是UTF-8。
注:
Unicode不是一次性定义的,而是分区定义。每个区可以存放65536个字符,称为一个平面(plane),定义了17个平面,目前Unicode字符集的大小是1,114,112。
- 最前面的
65536个字符位,称为基本平面(缩写BMP),是Unicode最先定义和公布的一个平面。
3、UTF-8
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~6个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8 的编码规则很简单,只有二条:
- 对于单字节的符号,字节的第一位设为
0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。 - 对于
n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码。
下表总结了编码规则,字母x表示可用编码的位。
1 | Unicode符号范围 | UTF-8编码方式 |
注:
- 如果一个字节的第一位是
0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。 - 2003年11月
UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是最多四个字节,非标准UTF-8仍支持使用1~6个字节表示一个符号。
严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。
常用中文编码范围(并非全部)
1 | /[\u4e00-\u9fa5]/ 中文:20902个 |
注:以上正则表达式是部分中文匹配,并非精确,但能满足大多数情况。
4、中文Unicode 编码范围
| 字符集 | 字数 | Unicode 编码 |
|---|---|---|
| 基本汉字 | 20902字 | 4E00-9FA5 |
| 基本汉字补充 | 38字 | 9FA6-9FCB |
| 扩展A | 6582字 | 3400-4DB5 |
| 扩展B | 42711字 | 20000-2A6D6 |
| 扩展C | 4149字 | 2A700-2B734 |
| 扩展D | 222字 | 2B740-2B81D |
| 康熙部首 | 214字 | 2F00-2FD5 |
| 部首扩展 | 115字 | 2E80-2EF3 |
| 兼容汉字 | 477字 | F900-FAD9 |
| 兼容扩展 | 542字 | 2F800-2FA1D |
| PUA(GBK)部件 | 81字 | E815-E86F |
| 部件扩展 | 452字 | E400-E5E8 |
| PUA增补 | 207字 | E600-E6CF |
| 汉字笔画 | 36字 | 31C0-31E3 |
| 汉字结构 | 12字 | 2FF0-2FFB |
| 汉语注音 | 22字 | 3105-3120 |
| 注音扩展 | 22字 | 31A0-31BA |
| 〇 | 1字 | 3007 |
5、UTF-16
UTF-16的编码规则很简单:基本平面的字符占用2个字节,辅助平面的字符占用4个字节。也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF)。UTF-16编码是UCS-2的超集,介于UTF-32与UTF-8之间,同时结合了定长和变长两种编码方法的特点。
| Unicode 编号范围 (十六进制) | 0000 0000 ~ 0000 FFFF | 0001 0000 ~ 0010 FFFF |
|---|---|---|
| Unicode 编号 (二进制) | xxxx-xxxx ~ xxxx-xxxx | xxxx xxxx xxxx xxxx xxxx |
| UTF-16 编码 | xxxx-xxxx ~ xxxx-xxxx | 110110xx xxxx xxxx 110111xx xxxx xxxx |
| 字节数 | 2 | 4 |
四字节存储:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位用一个值介于DC00~DFFF 之间的双字节存储。
注:标准的UTF-8和UTF-16都是最多支持U+0000到U+10FFFF的Unicode码点
6、Unicode与JavaScript
JavaScript语言采用Unicode字符集,但是只支持一种编码方法。这种编码既不是UTF-16,也不是UTF-8,更不是UTF-32。JavaScript用的是UCS-2。
注:在 level 3 或者更高等级的实现中,遵循国际标准,JavaScript 引擎是允许使用 UCS-2或者 UTF-16 进行编码的。
JavaScript 允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。 但是,这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。 ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
1 | "\u20BB7" |
- JavaScript 内部,字符以
UTF-16的格式储存,每个字符固定为2个字节。 使用for...of循环,它会正确识别 32 位的 UTF-16 字符
1 | let s = '𠮷a'; |
Array.from()可以将字符串转为数组,然后返回字符串的长度。它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的Unicode 字符,算作两个字符的 bug。
1 | Array.from(string).length |
codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法
1 | function is32Bit(c) { |