MUTF-8編碼格式介紹

在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個字節。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章