内容来自网络
在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个字节。