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
的Unicod
e 字符,算作两个字符的 bug。
1 | Array.from(string).length |
codePointAt
方法是测试一个字符由两个字节还是由四个字节组成的最简单方法
1 | function is32Bit(c) { |