UNICODE 基本知识
Unicode 及编码方式概述,这篇文章写得非常详细,不明白的细节可以参考那篇文章。
UTF-8 编码
UTF-8是UNICODE的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
- 对于单字节的符号,字节的第一位设为
0,后面7位为这个符号的 UNICODE 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的,因为首位是0,所以只能表示U+0000到U+007F范围内(十进制为0 ~ 127)。 - 对于 n 字节的符号(
n>1),第一个字节的前 n 位都设为1,第 n+1 位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 UNICODE 码。
下表总结了编码规则,字母 x 表示可用编码的位。
| unicode 符号范围 | utf-8 编码方式 |
|---|---|
| 十六进制 | 二进制 |
| 0000 0000-0000 007F | 0xxxxxxx |
| 0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
| 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
| 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
从上表可以看到:
- 首字节前几位可以看出这个
UNICODE字符的长度,如:110开头的则说明这个字符是2BYTE的UNICODE字符;1110开头则是3BYTE的UNICODE字符;
- 需要注意的是
单字节字符第一位上是0,而不是10; - 而
10开始的字节被用于 2 个字节长度以上的UNICODE字符的非首字节上。
所以从0,10,110,1110这些字节点的设计利用上可以看到UTF-8编码设计非常巧妙。
以汉字严为例,演示如何实现UTF-8编码。
已知严的 UNICODE 是4e25(100111000100101),根据上表,可以发现4e25处在第三行的范围内(0000 0800-0000 ffff),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是 "11100100 10111000 10100101",转换成十六进制就是e4b8a5。
在 firebug 中测试中文"胡"字:
console.log(encodeURI('胡'));// "%E8%83%A1"console.log('胡'.charCodeAt().toString(16));// "80e1"console.log('胡'.charCodeAt().toString(2));// "1000000011100001"console.log(parseInt('111010001000001110100001', 2));// 15238049console.log(parseInt('111010001000001110100001', 2).toString(16));// "e883a1" |
其中将1000000011100001对应位置补上110或者10,生成 unicode 二进制值111010001000001110100001,反过来从 unicode 转到 utf-8 只要移除对应位置上的110和10即可得到 utf-8 的二进制值。
关于 BOM 和字节序
虽然不是标准,但许多 Windows 程序(包括 Windows 记事本)在UTF-8编码的文件的开首加入一段字节串EF BB BF。这是字节序掩码U+FEFF的UTF-8编码结果。
而 POSIX 系统上明确不建议使用字节序掩码,即BOM(byte order mask),这会与bash脚本中开头的 bashbang(#!)有冲突,影响到脚本执行。
字节0xFE和0xFF在UTF-8编码中从未用到,同时,UTF-8以字节为编码单元,它的字节顺序在所有系统中都是一样的,没有字节序的问题,也因此它实际上并不需要BOM: byte-order-mark。
UTF-8 Encoding Scheme