黑马程序员技术交流社区

标题: 详细讲一下mutf-8 [打印本页]

作者: hei10215    时间: 2015-12-19 17:00
标题: 详细讲一下mutf-8
如题!

作者: kingjacob    时间: 2015-12-19 17:00
虽然我也不太会,但是我找到了一篇讲的挺全面,希望能帮到你


在Android应用程序的Dex文件中,所有的字符串都是使用一种叫做MUTF-8(Modified UTF-8)的编码格式进行编码的。

所谓的MUTF-8编码,其实是对UTF-16字符编码的再编码。

具体的实现可以查看MUTF-8编码的代码(代码位于libcore\dex\src\main\java\com\android\dex\Mutf8.java中):
public final class Mutf8 {
    ...
    public static void encode(byte[] dst, int offset, String s) {
        final int length = s.length();
        for (int i = 0; i < length; i++) {
            char ch = s.charAt(i);
            if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
                dst[offset++] = (byte) ch;
            } else if (ch <= 2047) {
                dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
                dst[offset++] = (byte) (0x80 | (0x3f & ch));
            } else {
                dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
                dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
                dst[offset++] = (byte) (0x80 | (0x3f & ch));
            }
        }
    }
    ...
}

如果UTF-16编码的字符,其值小于等于0x7F(127)的话,则MUTF-8直接用一个字节对其编码。这时,MUTF-8编码是完全和ASCII码兼容的。也就是说,如果字符串只使用了常用的一些可见字符的话,那么MUTF-8编码就基本上退化成了ASCII码。这里还有一个特例,如果UTF-16编码字符的值为0的话,MUTF-8编码将用两个字节来表示,而不是一个字节,因此要判断一下编码值非0。

所以,对于UTF-16编码字符的数值范围在0x1~0x7F之间的情况,MUTF-8编码格式如下:



因为对数值0做了特殊处理,所以经过MUTF-8编码后的值不可能为0(实际上0被MUTF-8编码用来表示字符串结束,和C语言的字符串表示法相兼容)。

接下来,代码要处理的情况是,UTF-16编码字符的数值范围在0x80~0x7FF之间的情况,当然还要包括0x0这种情况。

在这些情况下,MUTF-8编码将使用两个字节。对于第一个字节,前三个比特位是110,后面的5个比特位用来存放UTF-16编码字符数值的高5位。而对于第二个字节,前两个比特位是10,后面6个比特位用来存放UTF-16编码字符数值的低6位。对于数值为0x0的这种特殊情况,其MUTF-8编码后的值为0xC0和0x80。大致的编码格式如下图:



最后,如果UTF-16编码字符的数值范围在0x80~0xFFFF之间的话,MUTF-8将使用三个字节对其进行编码。

对于第一个字节,前四个比特位是1110,后面的4个比特位用来存放UTF-16编码字符数值的高4位。对于第二个字节,前两个比特位是10,后面6个比特位用来存放UTF-16编码字符数值的中间6位。而对于第三个字节,前两个比特位仍然是10,后面6个比特位用来存放UTF-16编码字符数值的最低6位。大致的编码格式如下:



在Android的官方Dex文件格式的文档中,对MUTF-8编码有如下描述,总结的很到位:

1)MUTF-8使用1到3个字节对UTF-16字符进行编码;

2)对于数值为0的情况,使用两个字节对其进行编码(编码后的值为0xC0和0x80);

3)采用类似于C语言中的空字符串(NULL,单字节数值为0)作为字符串结尾的标志;

4)对于UTF-16码点范围在U+10000到U+10FFFF的情况(补充字符),数值对中的每一个数值采用3字节对其编码。也就是说,对于这种情况,表示一个字符总共需要使用6个字节。

前面三点很好理解,对于第四点,理解起来有点困难,这里特别说明一下。

大家知道UTF-16使用16位来对字符进行编码,那么其取值范围就应该是0x0到0xFFFF,这已经可以表示很多字符了。但是,世界太大了,要表示的字符太多了,最终发现16位不够用了。那怎么办呢,只能继续扩展,将取值范围又向上扩展,从0x10000到0x10FFFF,称作扩展字符。这些扩展字符的值,显然不能再用16位来表示了,那就用两个16位值来表示。对于这种表示一个扩展字符使用两个16位数值的情况,UTF-16称作代替数值对(Surrogate Pair),其编码规则如下:

1)先将UTF-16补充码的数值减去0x10000;

2)将减掉之后的数值分为两个10比特的数值,假设高10位的值表示为Vh,低10位的值表示为Vl;

3)对于数值对中第一个16位的双字节来说,用0xD800加上高10位的值Vh;

4)对于数值对中第二个16位的双字节来说,用0xDC00加上低10位的值Vl。

具体的码表如下:



这里举个例子,假设要编码的UTF-16编码的数值为U+10437,编码步骤如下:

1)将数值将去0x10000,0x10437-0x10000=0x437;

2)0x437的二进制表示是0000 0000 0100 0011 0111,所以高10位是0000000001(也就是0x1),而低10位是0000110111(也就是0x37);

3)第一个16位双字节的值是0xD800+0x1=0xD801;

4)第二个16位双字节的值是0xDC00+0x37=0xDC37。

所以,UTF-16编码数值为U+10437的扩展字符,最终被UTF-16编码成0xD801和0xDC37。

还要注意一点,由于0xD800到0xDFFF都被UTF-16用来编码扩展字符了,所以这段范围内的数值会被UTF-16保留下来,不能表示其它任何字符了。

经过上面的解释,对于第四点就非常好理解了。由于UTF-16的扩展编码的两个16位数值的取值范围是在0xD800到0xDFFF,肯定是大于0x7FF的,因此处在MUTF-8编码的第三种情况下。所以,数值对中的每一个16位的值,MUTF-8都会使用3个字节对其进行编码。由于每个UTF-16的补充字符都需要用两个16位的值对来表示,所以MUTF-8编码过后会使用6个字节。
原地址http://bluereader.org/article/55078594
作者: zcbcba123    时间: 2015-12-19 17:10
只记得毕老师的录像里讲过一点儿
作者: zcbcba123    时间: 2015-12-19 17:13
你可以查查api,我记得那个视频是讲联通的那一集字符编码的
作者: sorryjsy    时间: 2015-12-20 13:41
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,Unicode 是国际标准码,融合了多种文字,所有文字都用两个字节表示,java语言使用的就是Unicode,而UTF-8是用三个字节来表示一个字符,而GBK是用两个字节来表示一个字符,说白了用UTF-8来表示一个字符,如果一个字节放不下就用两个,两个字节放不下就用三个,这就是可变长度字符编码,而且UTF-8每一个字节前面都有标识头,你可以查一下API文档(我是新手,说错勿喷,我是通过视频和百度总结出来的)。




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2