JAVA學習腳印10:解惑java 中UTF-16與char

不要回頭看發表於2020-10-15

 JAVA學習腳印10:解惑java 中UTF-16與char

java中的char、utf-16編碼、程式碼點、程式碼單元等概念,做一個瞭解還是有必要的。

1.基本概念

1) Java的字元型別和字串型別

 

字元型別採用的是UTF-16編碼方式對Unicode編碼表進行表示。其中一個char型別固定2位元組,為無符號數,表示範圍為'\u0000'(0)~'\uffff'(65,535)。

java中的String定義如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    ...

}

可見String內部使用char來儲存字元的,且是不可變的,即java字串是由char序列組成。

 

2) Unicode編碼表的專業術語:
 

a.  程式碼點 (code point): 指在Unicode編碼表中一個字元所對應的程式碼值。如漢字“一”的程式碼點是U+4E00,英文字母“A”的程式碼點是U+0041。
 

b.  程式碼單元( code unit): 規定16bits的儲存容量就是一個程式碼單元。java中的一個字元char就對應一個程式碼單元,大多數uncode字元使用一個程式碼單元就夠了,而輔助字元(見下文)需要一對程式碼單元。

可以這樣構造字元資料:

如 char[] chs = {'\u2764','\u2602','\u2600','\u262F','\u262D','\u2622','\u260E'};

上面這幾個字元比較流行,在GUI Jlabel中彩色列印出來如下圖所示:

可以通過網站: http://unicode-table.com/en/#control-character  

來獲取你想要的unicode字元。

 

c.  程式碼級別 (code plane): Unicode編碼表 ,分為17個程式碼級別 (code plane),其中程式碼點U+0000-U+FFFF為第一級別 ——基本多語言級別 (basic multiling l plane),可以用一個程式碼單元儲存一個程式碼點。其餘16個附加級別 從0x10000-0x10FFFF(需要兩個程式碼單元)。

其中需要指出的是在多語言級別中,U+D800-U+DFFF這2048值沒有表示任何字元,被稱為Unicode的替代區域(surrogate area)。UTF-16正是的運用了這一區域,用2個程式碼單元(2*16bits)巧妙的表示出20bits程式碼點的Unicode附加級別。

 

3)UTF-16編碼演算法

       假設U是一個程式碼點,也就是Unicode編碼表中一個字元所對應的Unicode值。
       1) 如果U<U+10000,也就是處於Unicode的基本多語言級別中。這樣16bits(一個程式碼單元)就足夠表示出字元的Unicode值。
       2) 如果U+10FFFF>U>=U+10000,也就是處於附加級別中。UTF-16用2個16位來表示出了,並且正好將每個16位都控制在替代區域U+D800-U+DFFF 中了。

附加級別中的編碼具體操作如下:

     分別初始化2個16位無符號的整數 —— W1和W2。其中W1=110110yyyyyyyyyy(0xD800-0xDBFF),W2 = 110111xxxxxxxxxx(0xDC00-OxDFFF)。

U' = U - 0x10000(注意,網上很多部落格中關於這個演算法的存在錯誤,錯誤之處就是沒有減去0x10000).

然後,將U'的高10位分配給W1的低10位,將U'的低10位分配給W2的低10位。這樣就可以將20bits的程式碼點U拆成兩個16bits的程式碼單元。

而且這兩個程式碼點正好落在替代區域U+D800-U+DFFF中。而且w1和w2具有可區分性,0xD800-0xDBFF屬於高代理項(high-surrogate),0xDC00-OxDFFF屬於低代理項(high-surrogate)。

 

2.例項說明

這裡舉一個網上很多部落格提到的比較經典的例子,U+1D56B:

該字元可以在 http://www.scarfboy.com/coding/unicode-tool?

網站上輸入程式碼點後檢視,看起來是這樣的,

假設U = U+1D56B ,則U屬於附加級別中的附加字元,那麼可如下計算編碼:

Step1:

U' = U- 0x10000 = 0x0D56B

       =   0000 1101 0101 0110 1011

Step2 :

         U'高十位:  0000 1101 01

         U'低十位:  01 0110 1011

         則w1 = 1101 1000 0011 0101  = 0xD835

            w2 = 1101 1101 0110 1011  = 0xDD6B

至此將U+1D56B 編碼為兩個程式碼單元,第一個為0xD835,第二個為 0xDD6B 。

下面的程式碼可以加深對上述概念的理解:

 

package com.learningjava;
 
/**
 * This programe try to illuminate UTF-16 in java
 * 
 * for unicode character refer to the following website :
 * http://www.scarfboy.com/coding/unicode-tool?
 * 
 * for more details refer to the book 《core java : volume 1》
 * 
 * for utf-16 algorithm refer to the following website :
 * http://en.wikipedia.org/wiki/UTF-16#Example_UTF-16_encoding_procedure
 * 
 * @author  wangdq
 * 2013-09-26
 */
 
public class UTF16Test {
    public static void main(String[] args) {
          String sample = null;
          if(args.length > 0) {
              sample = args[0];
          } else {
              // for the sake of not show the character of u+1D56B
              String special = new String(Character.toChars(0x1D56B));
              sample = special+" zZ";
          }
          System.out.println("sample string is:  "+sample);
          
          // String.length : Returns the length of this string.
          // The length is equal to the number of Unicode code units in the string.
          int len = sample.length();
          System.out.println("code units count:  "+len);
          // traverse the string by code units
          System.out.print("code units are:  ");
          UTF16Test.traverseByCodeUnits(sample);
          System.out.println();
          
          
          // get the number of Unicode code points
          int cpCount = sample.codePointCount(0, len);
          System.out.println("code points count:  "+cpCount);
          
          // traverse the string by code point s
          System.out.print("code points are:  ");
          UTF16Test.traverseByCodePoints(sample);
          System.out.println();
         
    }
    /**
     * traverse the string by code points
     * @param str the specified string to traverse
     */
    public static void traverseByCodePoints(String str) {
        int cpCount = str.codePointCount(0, str.length());
         for(int ix = 0;ix < cpCount;ix++) {
             printCodePoint(str.codePointAt(ix));
         }
    }
    /**
     * traverse the string by code units
     * @param str the specified string to traverse
     */
    public static void traverseByCodeUnits(String str) {
        int cuCount = str.length();
        for(int ix = 0;ix < cuCount;ix++) {
            String content = String.format("(%04x)  ",
                    (int)str.charAt(ix)).toUpperCase();
            System.out.print(content);//get code unit
        }
        
    }
    /**
     * print code point in hexadecimal form
     * @param cp the code point
     */
    private static void printCodePoint(int cp) {
        
        //check if is the supplementary code point
        if(Character.isSupplementaryCodePoint(cp)) {
            char[] chs = Character.toChars(cp);//stored the code point in  UTF-16 representation
            String content = String.format("[U+%04x,U+%04x]  ",
                    (int)chs[0],(int)chs[1]).toUpperCase();
            System.out.print(content);
        } else {
            String content = String.format("[U+%04x]  ",cp).toUpperCase();
            System.out.print(content);
        }
    }
}
 

不帶引數時執行結果為 :

 

 

更多關於字元和字串的操作可參考java官方API.

 

本文參考了部落格:blog.csdn.net/fantasy0126/article/details/6050087

相關文章