encoding长度问题
当我们在网络传输信息的时候,底层是按照0/1方式来传输,问题来了,当接收方接收到一连串0/1之后,接收方该如何分割0/1字符串呢?
ASCII一开始1字节(8位)来传输,后来GB2312采取变长方式来编码,这是因为当遇到汉字时采取2字节编码,遇到ASCII再用1字节编码,说白了GB2312动态变化主要是为了兼容1字节的ASCII。
再加上其他国家的国家标准,因此不同长度的编码方式层出不穷,为了方便计算机在全世界推广,解决不同国家编码长度不同的问题,为此发明了Unicode字符集。
Unicode一开始是按照固定2字节的方式来推广,即2^16次方个字符(有些软件Unicode字符集的编码默认是UTF16的原因),后来发现固定2字节还是不够,还得继续扩充。但是在扩容的时候发现一个问题:虽然字节越多,其表示的字符量越大,但是这会导致字符在内存中占据较大的容量,进而有点浪费内存。
如果采用定长编码方式的话,那么该定多长是个问题,另外有些生僻字符被用到的概率很小,即便用也就是用几次而已,有必要为小概率字符而设置较长的定长编码方式吗??????
UTF16采用2字节存储ASCII码,为了解决浪费问题,发明了UTF8来解决这个问题,当然UTF8也可以用3字节或者4字节来存储汉字,说实话,在中英文混杂的情况下,UTF8的效率比不上GBK、GB2312等。
因此结合上面信息,Unicode最后采用变长的编码方案。下面重点讲解变长的规则:
这里还是要回到编号上面,由于是一个二维平面,因此每个编号都会用一个“码点”来表示。
码点是用U+[XX]XXXX形式来表示,X代表一个十六制数字,最少为4位,最长为6位,不足4位的前补0补足4位,超过4位则按是几位就是几位。
码点取值范围时U+0000~U+10FFFF,这里是按照最短4位、最长6位的方式来编写,因此:
从U+0000到U+FFFF一共有65536个码点,
而从U+FFFF到U+10FFFF即有10个FFFF,由于是16进制,这里的10换成十进制就是17,也就是一共有17个65535。
为了方便管理这17个65535,Unicode把每65536个码点看成一个平面,这样就一共有17个平面(Plane),平面编号从0开始直到16。
其中第一个平面即Plane0叫做BMP,它的码点范围是U+0000到U+FFFF,该平面内的字符是我们最常用的平面,日常用到的字符绝大多数都落在这个平面内,因此当UTF-16遇到此平面内的字符时只需要用两字节来编码字符。
每个Plane都是一个256×256=65536的表格,横向/纵向都是从 00到FF。
Plane1-Plane16叫做增补平面(SP),对于这些平面内的字符,UTF-16采用了四字节编码。
码点与UTF-32转换
我们说码点最大的 10FFFF 也就 21 位,而
UTF-32采用的是定长4字节,即32位。而Unicode最大10FFFF 也就21位,所以只要把码点的表示形式以前补0的形式补够32位即可。
码点与UTF-8转换
UTF-8是变长的编码方案,其长度分为:1字节、2字节、3字节、4字节。
UTF-8采用高位保留方式来区别不同变长:
1字节:0XXXXXXX
2字节:110XXXXX 10XXXXXX
3字节:1110XXXX 10XXXXXX 10XXXXXX
4字节:11110XXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX
保留的高位是固定位,固定不变的。X表示是有效编码位。
1字节最高位都是0,多字节的最高位都是1。可以总结为:N字节模式(N>1),首字节以N个1再加0来打头,后跟N-1个以10打头的字节。
那码点具体如何从这4种编码方式中选择呢?
先把码点变成二进制,然后看它有多少有效位(去掉前导0)来确定编码方式:
1字节:因为1字节有效编码为就7个,因此码点有效编码位有7位的话,就用1字节来编码。1字节主要是留给ASCII,所以UTF-8兼容ASCII。码点 U+0000到U+007F(0~127)
2字节:2字节有效编码位只有11位,只有2^11=2048个编码空间,所以无法存放汉字。码点 U+0080到U+07FF(128~2047)使用二字节。
3字节:3字节有效编码位是16位,因此2^16=65536个,码点U+0800到U+FFFF(2048~65535)使用三字节编码。常用的汉字就落在这个区间。
4字节:4字节有效位是21 位,前面说到最大的码点10FFFF也是21位,U+FFFF以上的增补平面的字符都在这里来表示。
按照UTF-8的模式,它还可以扩展到5字节、6字节甚至更长,但Unicode规定码点就到10FFFF,不扩充了,所以UTF-8最多到四字节就足够了。
码点与UTF-16转换
UTF-16是一种变长的2或4字节编码模式。对于Plane0 BMP内的字符使用2字节编码,其它的则使用4字节组成所谓的代理对来编码。
先说说代理区:
00 | ... | FF |
... |
|
|
D8 |
|
|
D9 |
|
|
DA |
|
|
DB |
|
|
DC |
|
|
DD |
|
|
DE |
|
|
DF |
|
|
上面是Plane0即BMP中,纵向从D8到DF,这8行是空白行。其中D8到DB属于高代理区,而DC到DF属于低代理区。
这样各个代理区就有:4 * 256 = 1024个。
这样的话,高代理区有如下编号:
D800-D8FF D900-D9FF DA00-DAFF DB00-DBFF共1024个。
同样低代理区就有:
DC00-DCFF DD00-DDFF DE00-DEFF DF00-DFFF共1024个。
当把一个D8-DB共1024个编号当做竖坐标,而DC-DF当做横坐标,这个平面二维表格就有1024*1024=16*65536个表格。
而后面也恰恰有16个增补平面,即:Plane1-Plane16,正好可以容纳后面16个增补平面的字符量。
再说说代理对:
一个高代理区(D8-DB竖坐标)的加一个低代理区(DC-DF横坐标)的编码组成一对即是一个代理对,必须是这种先高后低的顺序。
再回到原题,即码点与UTF-16如何转换?
首先,BMP中直接对应的,无须做任何转换,直接2字节存储。
其次,位于增补平面SP中的,这就需要计算其代理对是多少,有点复杂,这里就不讲了。