java字符編碼-Unicode編碼問題刨根究底



博客搬家: java字符編碼問題

前段時間在讀《java核心技術卷一》,遇到一些名詞:碼點、代碼單元等,其實字面意思不難理解,解釋如下

  • 碼點(code point):Unicode編碼表中某個字符對應的代碼值
  • 代碼單元(code unit):用於UTF-16編碼的最小單元,16個bit

注意上述只是針對java中字符和字符串的Unicode+UTF-16機制的解釋。若是其他編碼方式就另說,如UTF-8的代碼單元是用8個bit編碼。

下面問題來了

書中建議,盡量不要使用char類型,最好將字符串轉化為抽象數據類型來處理,即codepoints數組

//將String轉化為碼點數組
int[] codePoints = str.codePoints().toArray();

那么為什么要這樣做呢,像c語言那樣直接使用char數組不好嗎?當然不行,因為在Unicode+UTF-16這種機制中,一個碼點可由一個代碼單元表示,但很多特殊字符也可能由兩個代碼單元表示。而char類型只能是一個代碼單元。所以,若字符串中存在特殊字符,遍歷char數組或者使用charAt等方法時就會出問題。但使用碼點數組就OK,因為數組中每一個元素都代表一個碼點,而不是一個代碼單元。而c語言中采用ASCII字符集,每個碼點都由一個8bit代碼單元表示,使用char數組則不存在這個問題。

這樣的描述對懂Unicode和UTF-16的人來說,很容易理解。但我想在我的博客中深究一下編碼機制背后的原理。為了便於小白理解,先介紹一下編碼規范的基本概念。

編碼規范

制定編碼規范為了將計算機能識別的二進制數,映射成人類能識別的字符。依據編碼規范,計算機就可以將二進制數顯示成字符。常見的編碼規范有,ASCII碼、GBK、ISO-8859-1、Unicode等。編碼規范中有三個子概念,字庫表、字符集、編碼方式。

字庫表

字庫表中存儲該編碼規范能表示的所有字符。一套編碼規范不一定能表示世界上所有的字符。例如GBK規范可以顯示漢字,但不能顯示法語、俄語等。

字符集

字庫表中每一個字符都有一個二進制地址,字符集就是這些二進制數的集合。例如 00000000 - 01111111 為ASCII字符集的范圍。

編碼方式

某些編碼規范包括大量的字符,例如Unicode中包含上百萬個字符。若每個字符都采用同樣長度的二進制數來編碼,即定長編碼,則要用三個字節來存儲,甚至在將來會用到四個字節。這樣很多本身只需要單字節存儲的字符也會占用三四個字節,會導致極大的資源浪費。

如果能采用一些算法,使得部分字符采用單字節編碼,部分采用雙字節等(即變長編碼),可節省不少資源,這些算法即為編碼方式。常見的編碼方式有UTF-8、UTF-16、UTF-32等。我前文所說的:Unicode+UTF-16機制,就容易理解了,即基於Unicode編碼規范並采用UTF-16編碼方式的機制。java中的字符串和字符正是采用這種機制進行編碼的,下面詳細介紹Unicode和UTF-16。

注意UTF的全稱是Unicode Transformation Format,含義為將Unicode轉換為某種格式。所以UTF-8、UTF-16等都是針對Unicode來說的。

Unicode

在Unicode出現之前,已經有了很多編碼規范,如美國的ASCII碼、中國的GBK、西歐的ISO-8859-1等,每一種規范都不能涵蓋所有國家的語言。Unicode設計的初衷就是將所有語言中的字符進行統一編碼。

Unicode最早的1.0版本中,字符集數量遠遠不到65536,因為當時的字符集不是那么龐大,使用2個字節編碼足夠使用,java也正是此時引進了16位的Unicode字符集。

然而,在Unicode增加了大量的漢語、日語、韓語字符之后,字符數量超過了65536,於是16位的char類型也就不能滿足了。實際上,這些海量的Unicode字符可以被划分為17個代碼級別(code plane)

  • 第一級別,基本多語言級別(basic multilingual plane),范圍U+0000~U+FFFF,下文稱其為基本平面
  • 其它16個級別,范圍U+10000~U+10FFFF,存儲輔助字符,下文把它稱作增補平面

UTF-16編碼則是對不同的代碼級別做文章,采用不同長度的編碼表示不同代碼級別的碼點

UTF-16

UTF-16使用16位作為一個代碼單元。基本平面的碼點采用一個代碼單元進行編碼,增補平面的碼點使用兩個代碼單元。

可能你會問,使用一對代碼單元表示一個增補平面的字符時,有沒有可能把它判別成兩個基本平面的碼點?也就是說,有沒有可能出現沖突的情況?

  • 當然不會,UTF-16中使用了代理機制,即使用基本平面中未映射字符的字符集區域,來作為增補平面中字符的代碼單元的區域

這些碼點區域稱為“替代區域”(syrrogate area),即U+D800 ~ U+DFFF范圍,該區域在基本平面中屬於空閑區域,2048個值。如此一來,便避免了沖突。

該替代區域分割為兩部分,U+D800 ~ U+DBFF用於第一個代碼單元,U+DC00 ~ U+DFFF用於第二個代碼單元。

代碼單元1 代碼單元2
1101 10pp ppxx xxxx 1101 11xx xxxx xxxx

pppp是指16個級別的級別編號,24=16。兩個代碼單元的變數部分(p和x)共20位,可表示220=1048576個碼點。而增補平面范圍U+10000~U+10FFFF恰好也是1048576個碼點。所以這兩個代碼單元可完美表示出增補平面中的所有字符。

這種方式十分巧妙。

事實上,只有UTF-16使用了“替代區域”方法,像現在被廣泛接納的UTF-8編碼,是通過首字節的比特位判斷碼點的代碼單元數量。

最后簡單介紹下UTF-16和UTF-8的區別,以后再找時間細究,把UTF-8的坑填上。

  • UTF-16使用2個或4個字節進行編碼,大部分漢字采用兩個字節編碼,少量漢字采用四個字節
  • UTF-8使用1個到4個字節編碼,大部分漢字采用三個字節


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM