关于字符编码 unicode utf-8 utf-16 utf-32

字符编码的那些事…

字符编码

字符串编码跟数值编码有所不同,毕竟全球普及阿拉伯数字和10进制,数值的编码会相对简单些 : 每个国家的字符集都有所不同,而且在随时随刻的增加.

ASCII

ASCII 使用一个字节,精确点说是 0-127 来表示 128 个字符, 对于 英文来说足够了

ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符
0 NUT 32 (space) 64 @ 96
1 SOH 33 ! 65 A 97 a
2 STX 34 66 B 98 b
3 ETX 35 # 67 C 99 c
4 EOT 36 $ 68 D 100 d
5 ENQ 37 % 69 E 101 e
6 ACK 38 & 70 F 102 f
7 BEL 39 , 71 G 103 g
8 BS 40 ( 72 H 104 h
9 HT 41 ) 73 I 105 i
10 LF 42 * 74 J 106 j
11 VT 43 + 75 K 107 k
12 FF 44 , 76 L 108 l
13 CR 45 - 77 M 109 m
14 SO 46 . 78 N 110 n
15 SI 47 / 79 O 111 o
16 DLE 48 0 80 P 112 p
17 DCI 49 1 81 Q 113 q
18 DC2 50 2 82 R 114 r
19 DC3 51 3 83 S 115 s
20 DC4 52 4 84 T 116 t
21 NAK 53 5 85 U 117 u
22 SYN 54 6 86 V 118 v
23 TB 55 7 87 W 119 w
24 CAN 56 8 88 X 120 x
25 EM 57 9 89 Y 121 y
26 SUB 58 : 90 Z 122 z
27 ESC 59 ; 91 [ 123 {
28 FS 60 < 92 / 124
29 GS 61 = 93 ] 125 }
30 RS 62 > 94 ^ 126 `
31 US 63 ? 95 _ 127 DEL

特殊字符解释

特殊字符 解释 特殊字符 解释 特殊字符 解释
NUL VT 垂直制表 SYN 空转同步
STX 正文开始 CR 回车 CAN 作废
ETX 正文结束 SO 移位输出 EM 纸尽
EOY 传输结束 SI 移位输入 SUB 换置
ENQ 询问字符 DLE 空格 ESC 换码
ACK 承认 DC1 设备控制1 FS 文字分隔符
BEL 报警 DC2 设备控制2 GS 组分隔符
BS 退一格 DC3 设备控制3 RS 记录分隔符
HT 横向列表 DC4 设备控制4 US 单元分隔符
LF 换行 NAK 否定 DEL 删除

编码

每个国家指定了自己的编码方式 比如 中文的 Big5 GB2312 , ISO-8859-x 系列等. 这样就非常不方便了:

  • GB2312 “中”编码为: D6D0
  • 阿拉伯编码(ISO 8859-6)中 D6D0 表示 : ضذ

非常不方便. 因此制定了Unicode标准,用来定义字符编码.

Unicode

Unicode是一个字符集 里面规定了世界上每个字符对应的二进制代码 至于这个二进制代码如何存储则没有任何规定.

Unicode 前面的字符可能只需要一个字节表示,例如: “A”, 后面需要两个字节, 例如 “汉” 的 Unicode 是 0x6c49 (110110001001001),需要 15 位 即 2个字节存储,更后面的字符可能更长如 3个字节甚至4个字节.

如果使用不定长存储:这时候就不能确定3个字节到底是1个字符(3字节长度),还是2个字符(1字节字符+2字节字符)

如果使用定长字节存储Unicode,比如 使用4个字节 则会造成浪费.

因此出现了UTF-8 UTF-16 UTF-32 等 存储方案:

UTF-8

Unicode(0x) 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

规则 :

  1. 单个字节字符,第一位 0,剩余 7 位对应字符的 Unicode 码点。
  2. 多个字节字符,需要几个字节,从头开始就有几个连续的 1,一个UTF-8编码组的其他的字节开头都是10,其余的位从右向左填充字符的 Unicode 码点

UTF-16

UTF-16使用2个字节或4个字节表示一个Unicode : 根据 Unicode 码点范围:

  • U+0x00000000 - U+0x0000FFFF : 使用2个字节表示,码点即UTF-16编码
  • U+0x00010000 - U+0x0010FFFF : 使用4个字节表示,首先计算 U’= Unicode -0x10000 然后表示为 20位2进制(Unicode 最大码点位是0x10FFFF 减去 0x10000 等于 0xFFFFF,因此可以表示为20位2进制): yyyy yyyy yyxx xxxx xxxx,则 UTF-16 编码就是 110110yyyyyyyyyy 110111xxxxxxxxxx
1
2
H = Math.floor((c-0x10000) / 0x400)+0xD800
L = (c - 0x10000) % 0x400 + 0xDC00

当读取时候

  • UTF-16中2个字节成为一个WORD,因此看到一个WORD属于范围 DB80(1101100000000000)-DBFF,那么紧接着的WORD肯定属于范围DC00(1101110000000000)-DFFF ,这两个WORD 是一个字符.
  • 如果一个WORD 小于 DB80 ,那么这个WORD 自成一个字符.

UTF-32

UTF-32编码以32位无符号整数为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。

BOM

前面讲到字节序,一个字符的编码可能涉及到多个字节,因此当存储时也需要定义字节序

Unicode UTF-16LE UTF-16BE UTF32-LE UTF32-BE
0x006C49 49 6C 6C 49 49 6C 00 00 00 00 6C 49
0x020C30 43 D8 30 DC D8 43 DC 30 30 0C 02 00 00 02 0C 30

UTF-8 不需要字节序 : 因为 UTF-8 肯定是 110xxxxx , 1110xxxx 这样的高字节在前, 这样才能知道需要读取后面的几个字节.也可以理解为 UTF-8 只有 大端字节序

UTF-16,UTF-32 则使用 FEFF (零宽无中断空格) 这个字符作为 BOM , 在传输时候首先传输这个字符 , 用来标识 UTF-16 / 32的字节序.

FEFF 小端为 FFFE(UTF-16)和FFFE0000(UTF-32), 在Unicode中都是未定义的码位,不会出现在实际传输中,只会在开头出现.因此只要碰到 FEFF 或者 FFFE 就认为是 字节流的开头.

UTF-16/32 传输和存储时首先传输 BOM 字节 用来确定字节序,UTF-8 不需要,但是也可以为了保持统一传输 EF BB BF (‭FEFF‬)

UTF BOM
UTF-8 without BOM
UTF-8 with BOM EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF