JAVA如何正確處理Unicode字符

最近在開發輸入法程序時遇到一個小問題,就是刪除一個emoji時,不能一次刪乾淨,需要執行兩次操作纔可以。Intuitively,這肯定是java操作unicode字符的問題,於是找了JAVA官方文檔參考一下,解決了這個問題,這裏做下簡單總結。原文在這裏,有興趣自己看。



http://www.oracle.com/technetwork/articles/java/supplementary-142654.html


注:文章中提到的“JAVA字節”均指JAVA平臺的16位字節,請不要和C的8位字節搞混。


首先需要知道標準Unicode字符是一個16位字符(廢話),所以標準Unicode字符集包含65536個字符(2的16次方嘛,哥的數學很好的)。但是僅僅65536個字符是遠遠不夠用的,尤其是爲了包含我們偉大中華民族豐富多彩的文字(反正我是不認識),Unicode字符集進行了擴展,擴展到了24位,也就是最多可以包含1,112,064個字符。這裏我們把標準Unicode字符集(也就是前65536個字符)叫做Basic Multilingual Plane (BMP),把超過16位以上的擴展字符集叫做supplementary characters。


UTF-16是一種編碼方式,以16位無符號單元來編碼Unicode字符,如果對一個標準Unicode字符編碼,只需要佔用一個UTF-16單元,如果對擴展Unicode字符編碼則需要佔用兩個UTF-16單元。


我們都知道在C語言中,一個primitive char佔用一個字節,也就是8位。但是在JAVA中,一個primitive char佔用16位,與一個標準Unicode字符長度相等,因爲JAVA平臺採用UTF-16進行編碼。這樣JAVA就很容易處理Unicode基本字符。但是對於Unicode的擴展字符,在JAVA中就需要佔用兩個char,也就是兩個UTF-16單元。


舉個栗子,大寫字母A的Unicode值爲U+0041,它屬於Unicode的基本字符集,所以它只佔用一個UTF-16單元,表示爲[0041]。而字符的Unicode值爲U+10400,它屬於擴展Unicode字符集,所以它佔用兩個UTF-16編碼單元,表示爲[D801][DC00](一箇中括號代表一個UTF-16單元,中括號本身沒意義)。第一個單元叫做high-surrogates,範圍從 U+D800 到 U+DBFF,第二個單元叫low-surrogates,範圍從 U+DC00 到 U+DFFF,這個看起來很類似多字節編碼。但是,我說的是但是,這裏有一個非常重要的不同點,從U+D800 到 U+DFFF 其實爲UTF-16的保留值範圍,專門用作編碼Unicode擴展字符集,這個範圍不被賦予任何實際的標準Unicode字符。也就是說,在你的程序裏只要判斷一個字符是否屬於這個範圍內,就可以知道這個字符是應該被當做一個獨立的Unicode基本字符處理,還是當做半個擴展Unicode字符。


回到我一開始提到問題,我在開發android輸入法的時候,當需要刪除一個emoji時,總是需要刪除兩次才能刪乾淨。這是因爲我用的emoji都是屬於Unicode擴展字符集的,在編輯框中佔用了兩個UTF-16單元,而每執行一次刪除操作只刪除了一個UTF-16單元,也就是一個JAVA字節,而另一個單元沒有被刪除,所以就會顯示異常。這時候只要再刪除掉另一個字節就算真正把這個emoji刪除乾淨了。


用戶在實際的應用中,肯定是把Unicode標準字符集和擴展字符集混合着使用,也就是說有的字符佔用1個java字節,有的字符佔用兩個java字節。那麼我在執行刪除一個字符的操作時必須也要先去判斷我是要一次性刪除一個字節還是刪除兩個字節。這就很簡單了,按照我前面提到的規則,先判斷一下被操作的字符值是否處於[U+D800,U+DFFF](inclusive)之間,如果是,就說明被操作的字符不是一個有效的標準Unicode字符,而是半個Unicode擴展字符,那麼只需要在程序中自動刪除兩個java字節就可以了。反之,如果被處理的字符不屬於[U+D800,U+DFFF]範圍內,那麼這個字符就是一個標準的Unicode字符,只需要按照一個java字節處理就可以了。


理論清楚後(我假設你清楚了,其實也不難,就是我表達的不清楚,沒辦法,工科人,總是思維超越語言),我再簡單介紹一下java中相關的處理函數。



  • Character.toChars(int codePoint):參數codePoint爲Unicode值,此函數將Unicode值轉換爲標準java字節數組。如果codePoint是Unicode標準字符,則返回值只包含一個char;如果codePoint是擴展Unicode字符,則返回值包含2個char。

    例:在程序中如果要輸出一個Unicode擴展字符,可以這樣String.valueOf(Character.toChars(0x1F60E))


  • Character.isLowSurrogate(char ch):判斷一個字符是否是一個Unicode擴展字符的低16位編碼。

  • Character.isHighSurrogate(char ch):判斷一個字符是否是一個Unicode擴展字符的高16位編碼。

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