字符编码与存储_UCS_UTF

1、字节和字符的区别

ASCII时代,字节和字符是一样的。当Unicode出现后,事情有所不同了。字节(octet)是一个八位的存储单元,取值范围一定是0~255。而字符(character,或者word)语言意义上的符号,范围就不一定了。例如在UCS-2中定义的字符范围为0~65535,它的一个字符占用两个字节。


2、UCS-2UCS-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+0000FFFFUCS-2是一样的。


要注意,UCS-2UCS-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
2
3
4
5
6
7
8
9
Unicode符号范围      |        UTF-8编码方式
(十六进制) | (二进制)
--------------------+-------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-001F FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0020 0000-03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0400 0000-7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

注:

  • 如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节
  • 2003年11月UTF-8RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000U+10FFFF,也就是最多四个字节,非标准UTF-8仍支持使用1~6个字节表示一个符号。


的 Unicode 是4E25100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5


常用中文编码范围(并非全部)

1
2
3
4
5
/[\u4e00-\u9fa5]/ 中文:20902个

/[\u0800-\u4e00]/ 日文:17921个

/[\uac00-\ud7ff]/ 韩文:11264个

注:以上正则表达式是部分中文匹配,并非精确,但能满足大多数情况。


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-32UTF-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-8UTF-16都是最多支持U+0000U+10FFFFUnicode码点


6、Unicode与JavaScript

JavaScript语言采用Unicode字符集,但是只支持一种编码方法。这种编码既不是UTF-16,也不是UTF-8,更不是UTF-32JavaScript用的是UCS-2

注:在 level 3 或者更高等级的实现中,遵循国际标准,JavaScript 引擎是允许使用 UCS-2或者 UTF-16 进行编码的。


JavaScript 允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。 但是,这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。 ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"\u20BB7"
// "₻7" JavaScript会理解成\u20BB+7

"\u{20BB7}"
// "𠮷"

"\u{41}\u{42}\u{43}"
// "ABC"

let hello = 123;
hell\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// true


  • JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。 使用for...of循环,它会正确识别 32 位的 UTF-16 字符
1
2
3
4
5
6
let s = '𠮷a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61
  • Array.from()可以将字符串转为数组,然后返回字符串的长度。它能正确处理各种 Unicode 字符,可以避免 JavaScript 将大于\uFFFFUnicode 字符,算作两个字符的 bug。
1
Array.from(string).length


codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法

1
2
3
4
5
6
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}

is32Bit("𠮷") // true
is32Bit("a") // false