【Java基礎】Java中的char是否可以存儲一箇中文字符之理解字符字節以及編碼集

Java中的一個char採用的是Unicode編碼集,佔用兩個字節,而一箇中文字符也是兩個字節,因此Java中的char是可以表示一箇中文字符的。

但是在C/C++中由於採用的字符編碼集是ASCII,只有一個字節,因此是沒辦法表示一箇中文字符的。

解答了上面的淺顯易懂的問題之後,下面徹底理清楚字符 字節以及編碼的原理。
其實關於編碼以及字節的問題,在騰訊實習生一面的時候也問到過,當時搞不懂面試官爲什麼會問這個問題,現在想想,這個問題還是很考驗一個人的思考以及鑽研深度的,而且這個問題遠遠比自己想象的複雜。

字符編碼

編碼的三個階段:

單字節字符 – ASCII
多字節字符 – ANSI
寬字節字符 – UNICODE

字符 字節 字符串

從第一個表和第二個表中可以看出來,任何字符再Unicode編碼中都是兩個字節進行標識的,而且世界上任何語言的字符的Unicode編碼都是不一樣的,因此不論計算機在什麼樣的語言環境下,都是可以正常顯示字符的。但是ANSI每個字符長度卻是不定的,這樣一個字符該如何解釋是和具體的計算機環境(具體的編碼規則)有關的。

字符集與編碼

各個國家和地區所制定的不同 ANSI 編碼標準中,都只規定了各自語言所需的“字符”。比如:漢字標準(GB2312)中沒有規定韓國語字符怎樣存儲。這些 ANSI 編碼標準所規定的內容包含兩層含義:

  1. 使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含“字符”的集合就叫做“字符集”
  2. 規定每個“字符”分別用一個字節還是多個字節存儲,用哪些字節來存儲,這個規定就叫做“編碼”

各個國家和地區在制定編碼標準的時候,“字符的集合”和“編碼”一般都是同時制定的。因此,平常我們所說的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。

“UNICODE 字符集”包含了各種語言中使用到的所有“字符”。用來給 UNICODE 字符集編碼的標準有很多種,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等

“編碼”的概念就是把“字符”轉化成“字節”但是每一種編碼對應着不同的轉換規則,如果不遵循規則,後果就是亂碼。

針對語言的字符和字節

就像開始所討論的一個char是否可以表示一箇中文字符的時候一樣,如果只是把char看做一種類型的話,那是否可以表示中衛字符取決於語言。而按此處的以語言進行劃分,就可以很容易看出兩種主流語言中字符與字節的差異了 – 在C++中char被看做是字節而在Java中被看做是字符。

編碼理解誤區

第一種誤解,往往是導致亂碼產生的原因
即採用每“一個字節”就是“一個字符”的轉化方法,實際上也就等同於採用 iso-8859-1 進行轉化。因此,我們常常使用 bytes = string.getBytes(“iso-8859-1”) 來進行逆向操作,先得到原始的“字節串”。然後再使用正確的 ANSI 編碼,比如 string = new String(bytes, “GB2312”),來得到正確的“UNICODE 字符串”

第二種誤解,往往導致本來容易糾正的亂碼問題變得更復雜

非 UNICODE 程序中的字符串,都是以某種 ANSI 編碼形式存在的。如果程序運行時的語言環境與開發時的語言環境不同,將會導致 ANSI 字符串的顯示失敗
比如,在日文環境下開發的非 UNICODE 的日文程序界面,拿到中文環境下運行時,界面上將顯示亂碼。如果這個日文程序界面改爲採用 UNICODE 來記錄字符串,那麼當在中文環境下運行時,界面上將可以顯示正常的日文。

亂碼場景

一個web開發初學者必定會遇到的亂碼問題及原理

當然這個問題在Android開發中也是經常遇到,尤其是在不注意設置UTF-8字符集的情況下,提交的都是GBK但是服務器卻按ISO8859-1來解釋

當頁面中的表單提交字符串時,首先把字符串按照當前頁面的編碼,轉化成字節串。然後再將每個字節轉化成 “%XX” 的格式提交到 Web 服務器。比如,一個編碼爲 GB2312 的頁面,提交 “中” 這個字符串時,提交給服務器的內容爲 “%D6%D0”。

在服務器端,Web 服務器把收到的 “%D6%D0” 轉化成 [0xD6, 0xD0] 兩個字節,然後再根據 GB2312 編碼規則得到 “中” 字。

在 Tomcat 服務器中,request.getParameter() 得到亂碼時,常常是因爲前面提到的“誤解一”造成的。默認情況下,當提交 “%D6%D0” 給 Tomcat 服務器時,request.getParameter() 將返回 [0x00D6, 0x00D0] 兩個 UNICODE 字符,而不是返回一個 “中” 字符。因此需要使用 bytes = string.getBytes(“iso-8859-1”) 得到原始的字節串,再用 string = new String(bytes, “GB2312”) 重新得到正確的字符串 “中”

數據庫中字符串類型數據的存儲

這個亂碼場景也比較容易遇到。舉個最簡單的例子是當字段的值含有中文的時候,如果存的時候與取的時候沒有顯式的指定編碼集,一般都會亂碼。比如存的時候按GBK或者GB2312這種默認字符集去存儲,但是解析顯示的時候卻默認的時候8859-1,自然就產生了亂碼。一個比較好的習慣當然是在配置環境的時候都設置編碼集爲UTF-8。

Unicode和UTF-X之間的關係

Unicode的實現方式不同於編碼方式。一個字符的Unicode編碼是確定的。但是在實際傳輸過程中,由於不同系統平臺的設計不一定一致,以及出於節省空間的目的,對Unicode編碼的實現方式有所不同。Unicode的實現方式稱爲Unicode轉換格式(Unicode Transformation Format,簡稱爲UTF)

例如,如果一個僅包含基本7位ASCII字符的Unicode文件,如果每個字符都使用2字節的原Unicode編碼傳輸,其第一字節的8位始終爲0。這就造成了比較大的浪費。對於這種情況,可以使用UTF-8編碼,這是一種變長編碼,它將基本7位ASCII字符仍用7位編碼表示,佔用一個字節(首位補0)。而遇到與其他Unicode字符混合的情況,將按一定算法轉換,每個字符使用1-3個字節編碼,並利用首位爲0或1進行識別。這樣對以7位ASCII字符爲主的西文文檔就大幅節省了編碼長度(具體方案參見UTF-8)。類似的,對未來會出現的需要4個字節的輔助平面字符和其他UCS-4擴充字符,2字節編碼的UTF-16也需要通過一定的算法進行轉換。

UTF-8就是在互聯網上使用最廣的一種Unicode的實現方式。其他實現方式還包括UTF-16(字符用兩個字節或四個字節表示)和UTF-32(字符用四個字節表示),不過在互聯網上基本不用.

UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。


    public class CharTest {

    public static void main(String[] args) throws UnsupportedEncodingException {

        String str = "hello中國";
        int byteLen = str.getBytes("utf-8").length;
        int strLen = str.length(); 
        System.out.println(System.getProperty("file.encoding"));
        System.out.println("byteLen : " + byteLen);
        System.out.println("strLen  : " + strLen);
    }
    }

分別將上述的str.getBytes(“utf-8”)設置爲gbk utf-8 utf-16 utf-32就可以看出每種編碼方式中,一箇中文字符所佔用的字節數是不一樣的。英文字符所佔字節數也是稍微不同的。

總結如下:
在 GB 2312 編碼或 GBK 編碼中,一個英文字母字符存儲需要1個字節,一個漢字字符存儲需要2個字節。
在UTF-8編碼中,一個英文字母字符存儲需要1個字節,一個漢字字符儲存需要3到4個字節。
在UTF-16編碼中,一個英文字母字符存儲需要2個字節,一個漢字字符儲存需要3到4個字節(Unicode擴展區的一些漢字存儲需要4個字節)。
在UTF-32編碼中,世界上任何字符的存儲都需要4個字節。

UTF-8的編碼規則:

  1. 對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。
  2. 對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼

Notepad++中進行編碼格式(ANSI,Unicode,Unicode big endian 和 UTF-8)的設置,然後使用UltraEdit進行16進制格式查看,就可以看出不同編碼方式下,同一個中文字符的編碼不同,字節數也不同。涉及到大小端問題還會導致順序的不同。後面的參考鏈接二中會有詳細的解釋

UTF-8 BOM和無BOM的區別
BOM(byte order mark)是爲 UTF-16 和 UTF-32 準備的,用於標記字節序(byte order)
還是一樣的,按上述的方式在notpad++中設置編碼格式然後在ultraedit中使用16進制進行查看就會發現默認utf-8的時候直接是

無bom的時候是:

一般還是直接使用無bom的編碼

大小端的問題

既然涉及到了字節,自然就涉及到大小端的問題。
在計算機系統中,以字節爲基本存儲單位的,每個地址單元都對應着一個字節,一個字節爲8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對於位數大於8位的處理器,例如16位或者32位的處理器,由於寄存器寬度大於一個字節,那麼必然存在着一個如果將多個字節安排的問題。因此就導致了大端存儲模式和小端存儲模式。

大小端定義:
1) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
2) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。

網絡傳輸一般採用大端序,也被稱之爲網絡字節序,或網絡序。IP協議中定義大端序爲網絡字節序。Berkeley套接字定義了一組轉換函數,用於16和32bit整數在網絡序和本機字節序之間的轉換。htonl,htons用於本機序轉換到網絡序;ntohl,ntohs用於網絡序轉換到本機序。
關於網絡字節序,Unix高級網絡編程中有詳細的解釋

參考鏈接:
https://zh.wikipedia.org/wiki/Unicode
http://www.regexlab.com/zh/encoding.htm
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://blog.csdn.net/ce123/article/details/6971544

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