首頁  >  文章  >  Java  >  詳細介紹Java中codepoint和UTF-16相關的一些事

詳細介紹Java中codepoint和UTF-16相關的一些事

黄舟
黄舟原創
2017-03-21 11:11:401534瀏覽

Unicode和UTF-8/UTF-16/UTF-32的關係式

Unicode和UTF-8/UTF-16/UTF-32之間就是字元集和編碼的關係。字符集的概念實際上包含兩個方面,一個是字符的集合,一個是編碼方案。字符集定義了它所包含的所有符號,狹義上的字符集並不包含編碼方案,它只是定義了屬於這個字符集的所有符號。但通常來說,一個字元集並不僅僅定義字元集合,它還為每個符號定義一個二進位編碼。當我們提到GB2312或ASCII的時候,它隱式地指明了編碼方案是GB2312或ASCII,在這些情況下可以認為字元集與編碼方案互等。

但是Unicode有多種編碼方案。 Unicode字符集規定的標準編碼方案是UCS-2(UTF-16),以兩個位元組表示一個Unicode字符(UTF-16中兩個位元組的基本多語言平面字符, 4個位元組的為輔助平面字元)。而UCS-4(UTF-32)用4個位元組表示一個Unicode字元。另外一個常用的Unicode編碼方案–UTF-8用1到4個變長位元組來表示一個Unicode字符,並且可以從一個簡單的轉換演算法從UTF-16直接得到。所以使用Unicode字元集時有多種編碼方案,分別用於適當的場景。

再通俗一點地講,Unicode字元集就相當於是一本字典,裡面記載著所有字元(即圖像)以及各自所對應的Unicode碼(與具體編碼方案無關),UTF-8/ UTF-16/UTF-32碼就是Unicode碼經過對應的公式計算得到的並且實際儲存、傳輸的資料。

UTF-16

JVM規格中明確說明了java的char型別所使用的編碼方案是UTF-16,所以先來了解下UTF-16。

Unicode的編碼空間從U+0000到U+10FFFF,共有1112064個碼位(code point)可用來映射字符,,碼位就是字符的數字形式。這部分編碼空間可以分割成17個平面(plane),每個平麵包含2^16(65536)個碼位。第一個平面稱為基本多語言平面(Basic Multilingual Plane, BMP),或稱為第零平面(Plane 0)。其他平面稱為輔助平面(Supplementary Planes)。基本多語言平面內,從U+D800到U+DFFF之間的碼位區塊是永久保留不對應到Unicode字元。 UTF-16就利用保留下來的0xD800-0xDFFF區段的碼位來對輔助平面的字元的碼位進行編碼。

最常用的字元都包含在BMP中,以2個位元組表示。輔助平面中的碼位,在UTF-16中被編碼為一對16位元長的碼元,稱為代理對(surrogate pair),具體方法是:

  • ##將碼位減去0×10000,所得的值的範圍為20位元長的0~0xFFFFF。

  • 高位的10位元的值(值的範圍為0~0x3FF)被加上0xD800得到第一個碼元或稱為高位代理(high surrogate),值的範圍是0xD800~0xDBFF.由於高位代理比低位代理的值要小,所以為了避免混淆使用,Unicode標準現在稱高位代理為前導代理(lead surrogates)。

  • 低位的10位元的值(值的範圍也是0~0x3FF)被加上0xDC00得到第二碼元或稱為低位元代理(low surrogate),現在值的範圍是0xDC00~0xDFFF.由於低位代理比高位代理的值要大,所以為了避免混淆使用,Unicode標準現在稱低位代理為後尾代理(trail surrogates)。

例如U+10437編碼:

  • 0×10437減0×10000,結果為0×00437,二進位為0000 0000 0100 0011 0111。

  • 分區它的上10位元值和下10位元值(使用二進位):0000000001 and 0000110111。

  • 新增0xD800到上值,以形成高位元:0xD800 + 0×0001 = 0xD801。

  • 新增0xDC00到下值,以形成低位元:0xDC00 + 0×0037 = 0xDC37。

由於前導代理、後尾代理、BMP中的有效字元的碼位,三者互不重疊,搜尋時一個字元編碼的一部分不可能與另一個字元編碼的不同部分相重疊。所以可以透過只檢查一個碼元(構成碼位的基本單位,2個位元組)就可以判定給定字元的下一個字元的起始碼元。

java中的codepoint相關

對於一個字串對象,其內容是透過一個char數組儲存的。 char類型由2個位元組存儲,這2個位元組實際上儲存的就是UTF-16編碼下的碼元。我們使用charAt和length方法的時候,返回的實際上是一個碼元和碼元的數量,雖然一般情況下沒有問題,但是如果這個字符屬於輔助平面字符,以上2個方法便無法得到正確的結果。正確的處理方式如下:

int character = aString.codePointAt(i);
int length = aString.codePointCount(0, aString.length());

要注意codePointAt的回傳值,是int而非char,這個值就是Unicode碼。

codePointAt方法呼叫了codePointAtImpl:

static int codePointAtImpl(char[] a, int index, int limit) {
        char c1 = a[index];
        if (isHighSurrogate(c1) && ++index < limit) {
            char c2 = a[index];
            if (isLowSurrogate(c2)) {
                return toCodePoint(c1, c2);
            }
        }
        return c1;
    }

isHighSurrogate方法判斷下標字元的2個位元組是否為UTF-16中的前導代理(0xD800~0xDBFF):

public static boolean isHighSurrogate(char ch) {
        // Help VM constant-fold; MAX_HIGH_SURROGATE + 1 == MIN_LOW_SURROGATE
        return ch >= MIN_HIGH_SURROGATE && ch < (MAX_HIGH_SURROGATE + 1);
    }
public static final char MIN_HIGH_SURROGATE = &#39;\uD800&#39;;
public static final char MAX_HIGH_SURROGATE = &#39;\uDBFF&#39;;

然后++index,isLowSurrogate方法判断下一个字符的2个字节是否为后尾代理(0xDC00~0xDFFF):

public static boolean isLowSurrogate(char ch) {
        return ch >= MIN_LOW_SURROGATE && ch < (MAX_LOW_SURROGATE + 1);
    }
public static final char MIN_LOW_SURROGATE  = &#39;\uDC00&#39;;
public static final char MAX_LOW_SURROGATE  = &#39;\uDFFF&#39;;

toCodePoint方法将这2个码元组装成一个Unicode码:

public static int toCodePoint(char high, char low) {
        // Optimized form of:
        // return ((high - MIN_HIGH_SURROGATE) << 10)
        //         + (low - MIN_LOW_SURROGATE)
        //         + MIN_SUPPLEMENTARY_CODE_POINT;
        return ((high << 10) + low) + (MIN_SUPPLEMENTARY_CODE_POINT
                                       - (MIN_HIGH_SURROGATE << 10)
                                       - MIN_LOW_SURROGATE);
    }

这个过程就是以上将一个辅助平面的Unicode码位转换成2个码元的逆过程。

所以,枚举字符串的正确方法:

for (int i = 0; i < aString.length();) {
	int character = aString.codePointAt(i);
	//如果是辅助平面字符,则i+2
	if (Character.isSupplementaryCodePoint(character)) i += 2;
	else ++i;
}

将codePoint转换为char[]可调用Character.toChars方法,然后可进一步转换为字符串:

new String(Character.toChars(codePoint));

toChars方法所做的就是以上将Unicode码位转换为2个码元的过程。

以上是詳細介紹Java中codepoint和UTF-16相關的一些事的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn