前言
说到编码,首先要搞清楚 unicode、utf-8、utf-16,至于其他的,可以暂时不管,搞懂这三个就可以无师自通了。
预备知识
对于计算机来说,任何字符其实都是一张图片,计算机把这个图片内容绘制在屏幕上,我们就能看到它了。
为了便于传输与编辑,人们事先在每个计算机上存好一套字符库,记录了每个字符的样子,其实就是字体文件。每个字符对应一个ID,这样计算机处理文字以及传输文字时,就能只处理这些ID,实际上就是一连串的整数。
由于历史原因,我们的前辈们制订了许多套编码标准(字符映射方案),其中 Unicode 便是其中一种。
举个例子:在Unicode编码方案中,汉字 丘
对应的整数值为 19992
(此为十进制表示),十六进制表示为 4E18
,当然更规范的表示形式为U+4E18
。
这个Unicode编码方案是世界通用的,因此很多地方都是用的Unicode。
Unicode 的编码范围为 0x000000~0x10FFFF,大概能表示110多万个字符,这里有着全部的unicode字符表:https://unicode-table.com/
乍一看,每个字符需要用3个字节才能容纳,由于Unicode编码最大的数值才 0x10FFFF,其实只需要 21 个比特就可以了,3个字节(24位)绰绰有余了。然而实际上,使用这种等宽编码存储 会非常浪费空间,尤其是以英语为母语的国家,人家也就几十个字母和符号,每个字符却都要用3个字节存储太浪费。
UTF-8编码规则
所以针对这个问题,出现了 utf-8,utf-8使用变长编码,具体对应规则如下:
- 对于 U+00至U+7F,直接单字节编码,unicode值即为编码值。
例如:大写字母A
,为U+41,对应utf-8编码就是 :0100 0001
。 - 对于 U+80至U+7FF,最大数值不超过11个比特位,使用两个字节存储,形式为:
110xxxxx
10xxxxxx
,其中前一字节的110为固定形式,后一字节的10也是固定形式,剩下的位拼合起来刚好 11 个比特位。举例:字符Ψ
,U+03A8,二进制为:0000 0011 1010 1000
,只留低11位,011 1010 1000
,然后把高五位01110
填充到上面前一字节,低六位101000
填充到后一字节,最终为:1100 1110
1010 1000
。
可以在Notepad++或其他文本编辑器上测试一下。
首先,编辑器编码选择utf-8。
然后,使用十六进制编辑输入 CE A8
。
最后,关闭十六进制编辑,看看显示的字符是什么。
3. 对于 U+800至U+FFFF,编码最大值不超过16个比特位。与双字节类似,形式为:1110xxxx
10xxxxxx
10xxxxxx
,其中用于编码的位 刚好 16 个比特位,对于一个Unicode 编码,取其低 16 位,然后再由高到低依次填充。
举例:字符丧
,U+4E27,二进制形式:0100 1110 0010 0111
,填充进去,最终为:11100100
10111000
10100111
,同样可以用十六进制编辑测试一下,输入E4 B8 A7
,查看显示内容。
4. 对于 U+10000至U+10FFFF,编码最大值不超过21个比特位。与双字节类似,形式为:11110xxx
10xxxxxx
10xxxxxx
10xxxxxx
,其中用于编码的位 刚好 21 个比特位,对于一个Unicode 编码,取其低 21 位,然后再由高到低依次填充。
由于这种偏僻的字符不好显示,举例省略。
区分
单字节: 0xxxxxxx
双字节: 110xxxxx
10xxxxxx
三字节: 1110xxxx
10xxxxxx
10xxxxxx
四字节: 11110xxx
10xxxxxx
10xxxxxx
10xxxxxx
可以看到,有很明显的区分特征,所以程序读取时,不会读错。
UTF-16编码规则
虽然Unicode编码范围是 0x000000 ~ 0x10FFFF,但其实并不是全部都用满了,把全世界各种乱七八糟的字符安排上,仍然有很大的空间未被使用。其中就有一些编码范围被拿来用在了特殊用途,而不是用来代表字符。
被用来当做特殊用途的编码范围是 U+D800 ~ U+DFFF。
utf-16 把字符用 双字节表示,或者用 四字节表示。
具体规则如下:(和 utf-8 完全不一样)
- 对于 U+0000 ~ U+FFFF,并把 U+D800 ~ U+DFFF 抠出去,直接双字节编码,即二进制位不变化。
- 对于 U+10000 ~ U+10FFFF,首先减去 0x10000,则区间范围由
[0x10000, 0x10FFFF]
变为[0x00000, 0xFFFFF]
,此时最大值不超过 20 个比特位,取变换后的编号的低20位,将这20个比特位分为高10位和低10位,那么10个比特位的变化范围仅有[0x000, 0x3FF]
,高10位 加上0xD800
,低10位 加上DC00
,那么高10位 变化范围为[0xD800, 0xDBFF]
,低10位 变化范围为[0xDC00, 0xDFFF]
,而这两段范围恰好是 抠出去的 编码范围。
这样,每读取16位,如果恰好是 0xD800 ~ 0xDFFF,这个范围,说明它是4个字节存储的,还得往后再读取16位。再按照编码规则逆回去,就能得到真正地 Unicode 编码。
java中的字符与字符串
java中的 char 类型,j每个字符占2字节,使用的是 utf-16 存储,由于它只占2个字节,因此对于 U+10000 ~ U+10FFFF 这个范围的字符,不能使用char类型变量存储。强行用则报编译错误。不过好在 常见的汉字、日韩文字、英法字母、还有其他一些符号 都在 U+0000 ~ U+FFFF范围内。
需要注意的是,Unicode并没有把所有的汉字包含在内,~~可能嫌弃汉字太多,大部分编码全分给汉字不公平,我瞎猜的。~~还有的 汉字连字典上都没有,生活中也不可能用到。
java 中的字符串 String 是可以存储任意字符的。