本文目錄一覽:
- 1、字符編碼簡述
- 2、java的String.getBytes()方法,編碼問題
- 3、java的數字與字符的強轉 ,究竟按照那種編碼方式來轉跟什麼有關係?
- 4、JAVA字符編碼問題
- 5、java編程題 字符編碼aaaaaaabbbbbcerrrrggggggggsssssspoq
字符編碼簡述
眾所周知,java中如果要計算一個字符串的長度,可以直接利用String的length方法。如下:
顯然,這裡的length方法計算的字符數,一個英文字母按一個字符計算,一個中文漢字也是按照一個字符進行計算的。
不過,如果想要獲取字符串的字節數呢?String依然提供了現成的方法供我們使用,如下所示:
這裡,可以看到幾個注意點:
先來看第一點,也是本文主要想討論的問題:UTF-8、GBK的區別是什麼,為什麼會導致最終獲取的字節數不一樣?
要解答上面的問題,需要先知道GBK和UTF-8分別是什麼。
簡單的說,GBK和UTF-8是兩種字符的編碼方式。那麼,問題又來了,什麼是字符的編碼方式呢?除了GBK和UTF-8,有沒有其他的編碼方式呢?其中的區別又在哪裡?
關於字符的編碼方式,姑且可以簡單的理解為,將一個字符表示成一串bit流的規則(這個說法是不太準確的,下文會有詳細解釋)。比如說,UTF-8就是一種非常常用的字符編碼方式,“漢”字以UTF-8的規則計算後表示出來的bit流就是“11100110 10110001 10001001”。
有些時候,編碼方式,還會被稱為編碼規則、編碼方案。
實際上,從計算機不再單純地拿來進行數字計算開始,字符的編碼方式就一直在不斷的演進,現在就藉著這一段歷史,來對包括GBK、UTF-8在內的幾種常見字符編碼方式進行下介紹。
計算機剛出世的時候,美國人為了交流通信方便,約定了一套字符編碼方式,就是ASCII碼。
ASCII全稱為American Standard Code for Information Interchange,即美國信息互換標準碼。
ASCII碼的字符集中包含了26個英文字母、10個數字(0-9)、一些常見的符號(@、#、!),基本能夠滿足在英語環境下的需求。ASCII字符集裡面只有128個字符,每個字符都有一個編號,也就是0-127。而當時大家已經習慣於用8個bit來表示一個字節,所以乾脆取一個字節來表示一個字符。其中,最高位置為0,其他位全部用上,總共128個位置,剛好能夠與ASCII字符集一一對應。
舉個例子,在ASCII碼中,‘A’對應的編號是65,用一個字節表示就是“01000001”。
這裡對引入的兩個新概念做下解釋:
字符集 :字面上理解就是字符的集合。
編號字符集 :指帶有數字編號的字符集合,有時候也簡稱為字符集。例如:[1:a, 2:b, 3:c],在此字符集中,包含三個字符:a、b、c,並且其編號分別為1,2,3。
不過,後來計算機傳到了歐洲,不少歐洲國家的語言使用ASCII碼無法完整地進行表示,比如德語、法語。上文可以看到,在ASCII編碼中,一個ASCII字符,是用一個字節來表示的。一個字節實際上能夠表示256個數字,也就至少能夠表示256個字符,而ASCII字符集只有128個字符。所以這時候出現了多種基於ASCII的編碼方式。大家的基本思路都是一樣的:還是使用一個字節表示一個字符,0-127依然用來表示ASCII字符集(字符編號與ASCII碼保持一致),128-255拿來表示自己語言中的特殊字符。
顯然,這麼搞出來的多個編碼方式互不兼容,大家會很痛苦。所以最後出現了兩套統一的編碼方案,能夠對歐洲各國的字符都進行支持。這兩套編碼方案分別是:EASCII(Extended ASCII)字符編碼方案,ISO/IEC 8859字符編碼方案。
這兩套方案也是沿用上面的思路:0-127依然用來表示ASCII字符集(字符編號與ASCII碼保持一致),128-255用來表示歐洲各國的特殊字符(這部分字符集又被稱為擴展字符集)。
由於在這兩種編碼方案中,ASCII字符集中的字符,保留了與ASCII碼相同的字符編號,所以 這兩種編碼方案都是對ASCII編碼完美兼容的 。
不過,與ASCII、EASCII屬於單個獨立字符集不同,ISO/IEC 8859是一組字符集的統稱。其下共有15個字符集,即ISO/IEC 8859-n,n=1,2,3 …… 15,16(其中12未定義,所以共15個)。
到現在為止,EASCII已經很少有人用了,ISO/IEC 8859卻是被廣泛使用,其中ISO/IEC 8859-1被使用的最為普遍。而ISO/IEC 8859-1又被簡稱為ISO 8859-1,而且它還有一個Latin-1(也寫作Latin1)的簡稱。
終於,計算機來到了中國。如上文所述,仿照ASCII碼的規則,1個字節最多也就只能表示256個字符。但是,中國漢字有幾萬個,常用字就有幾千個,這樣的話,1個字節是完全不夠用的。所以,當時的全國信息技術標準化技術委員會搞了一套自己的編碼方案:用兩個字節表示一個字符。這就是GB系列編碼。“GB”是“國標”的拼音首字母縮寫,意為“國家標準”。
最早的GB編碼就是GB2312,收錄了6763個漢字和682個符號,基本能夠滿足日常需求。
GB2312規定,一個漢字的編號必須大於127,並且編號大於127的字符必須用兩個字節來表示。而0-127,仍然用來表示之前的ASCII字符集,這部分字符的編號依舊與ASCII碼保持一致,並且只有一個字節來表示。
所以,GB2312對ASCII碼是完全兼容的。不過GB2312對ISO是不兼容的,因為它捨棄了ISO中128-255之間的字符映射。
同時,也可以認為,在GB2312中,英文字符只佔一個字節,而中文字符會佔兩個字節。
而計算機在依照GB2312編碼進行字符識別時,會先判斷第一個字節的第一個bit位是否為0,如果是,則讀取1個字節,進行編碼解析;如果不是,則讀取兩個字節,進行編碼解析。
此外,當時出於種種原因考慮,GB2312對ASCII碼中的西文字母、數字、標點等特殊符號進行了重新編碼,用兩個字節來進行表示。所以,這類字符在GB2312中就有了兩種編碼表示,其中小於128的編碼(用1個字節表示),就被稱為半角字符,大於128的編碼(用2個字節表示),就被稱為全角字符。
到目前為止,由於當時導致全角字符出現的歷史原因已經不再存在,所以只有很少的一些全角字符還在使用(比如中文的逗號,問號,感嘆號,空格等),其他的許多全角字符已經很少用了。
雖然GB2312能夠滿足基本的日常需求,但是畢竟收錄的漢字還是太少,繁體字、生僻字是不包含在GB2312字符集中的。由此,有關部門對GB2312進行了擴展,推出了GBK編碼。
GBK與GB2312基本一致,都是使用兩個字節來表示漢字。不過有一點不一樣:在GB2312中,表示漢字的兩個字節中,其首位必須都是1;而在GBK中,只要求第一個字節(高字節)的首位為1,對於第二個字節(低字節),沒做要求。當然,如果首位為0,都是用來表示ASCII字符集里的內容。
GBK可以認為是對GB2312的擴展,其對GB2312是完美兼容的。所以,GBK對ASCII碼也是完美兼容的。
GB18030是對GBK的進一步擴展,在擴展現有漢字的基礎上,收錄了數千個少數民族的字符。其由中國國家質量技術監督局於2000年3月17日推出,用以取代GBK。
GB18030同樣保持向下兼容,其對GBK、GB2312、ASCII編碼完美兼容。
諸如GB2312、GBK、GB18030之類的編碼格式,被程序員們稱為DBCS(Double Byte Charecter Set:雙字節字符集)。在DBCS的標準里,英文字符用一個字節表示,並且這個字節的第一位必然為0(英文字符對應的字號小於128);中文字符用兩個字節表示,第一個字節的第一位必然為1。
如上文所述,在計算機的傳播途中,為了兼容各地的語言,出現了許許多多的編碼方案。但是遺憾的是,這些編碼方案互不兼容,直接影響到了信息的傳播,這也催生了能夠兼容全球各種字符的統一編碼方案的出現。
歷史上存在兩個獨立的嘗試創立單一字符集的組織:
不過在1991年前後,兩個項目組發現沒必要存在兩個不兼容的字符集,所以它們開始合併雙方成果,約定使用統一的編碼表。從Unicode 2.0開始,Unicode項目採用了與ISO 10646-1相同的字庫與字碼,ISO也承諾,ISO將不會替超出U+10FFFF的UCS-4編碼賦值,以使得兩者保持一致(UCS的概念下文會有詳述,此處不必過於關注)。
目前,這兩個項目組仍獨立存在,並獨立地發布各自的標準,不過二者約定保持雙方的標準碼錶兼容,並共同調整任何未來的擴展。
ISO 10646標準,只是一個簡單的字符集表。它定義了一些編碼的別名,指定了一些與標準有關的術語,並包括了規範說明,指定了怎樣使用UCS鏈接其他ISO標準的實現,比如ISO/IEC 6429和ISO/IEC 2022。還有一些與ISO緊密相關的,比如ISO/IEC 14651是關於UCS字符串排序的。
Unicode標準,額外定義了許多與字符有關的語義符號學內容,並詳細說明了繪製某些語言(如阿拉伯語)表達形式的算法、處理雙向文字(比如拉丁文和希伯來文的混合文字)的算法、排序與字符串比較所需的算法等。
在書寫Unicode編碼時,規定以十六進制數來進行表示,並需要加上“U+”前綴。比如“漢”字的Unicode編碼為“U+6C49”。
為了能夠更方便地介紹後續的內容,這裡需要先解釋清楚幾個名詞(個人認為這幾個概念有助於理解後續的內容,如果不想看,可以直接跳過此節)。
編號字符集(CCS:Coded Character Set) :指帶有數字編號的字符集合。上文已經介紹過了。
字符編碼方式(CEF:Character Encoding Form) :將字符集的數字編號轉換為字節流的規則。
還是上文中的例子,Unicode字符集中的“漢”字,在Unicode字符集中的編號是0x6C49,在UTF-8編碼中,需要使用3個字節來表示,表示成二進制則是“11100110 10110001 10001001”(UTF-8的具體編碼規則,下文會有詳述)。
在這個例子中,Unicode就是所謂的編號字符集(CCS),UTF-8編碼便是字符編碼方式(CEF)。
實際上,在unicode字符集出世之前,字符集與編碼方式往往是耦合在一起的,一套字符集往往也只有一套編碼規則,這兩個概念也沒必要嚴格區分,人們也經常進行混用。比如ASCII碼既可以認為是一套字符集,也可以認為是一種字符編碼方式。
但是,Unicode字符集出現之後,字符集和編碼方式被分離解耦了。此時,一套字符集可能有多套的編碼規則,我們所熟知的UTF-8、UTF-16就是建立在Unicode字符集上的字符編碼方式。
編碼規則大致上可以分為兩類:直接映射與間接映射。
直接映射 ,是指字符在字符集中的數字編號與編碼後的編碼串是一樣的。比如ASCII字符集中,‘A’對應的字符編號是65,換算成二進制為“1000001”,按照ASCII碼編碼後,用一個字節來表示,就是“01000001”,也就是十進制中的65。編碼前後,其實可以視為是一樣的。
間接映射 ,就是字符在字符集中的數字編號與編碼後的編碼串不一定一樣。還是上面的例子,unicode字符集中“漢”字的字符編號為0x6C49,如果換算成二進制就是“01101100 01001001”,但是UTF-8編碼後要用三個字節來表示,表示成二進制就是“11100110 10110001 10001001”。編碼前後,數值不一樣。
其實,Unicode出現之前,大家一直用的都是直接映射,編碼前後數值是一樣的,這也是一直沒有明確區分字符集和編碼方式這兩個概念的一個原因。
解釋清楚了這幾個概念,下面我們繼續:
UCS全稱為“Unicode Character Set”,是由ISO制定的ISO 10646標準所定義的標準字符集。
UCS又稱“Universal Multiple-Octet Coded Character Set”,譯為通用多八位編碼字符集。
相對應的,Unicode項目所使用的標準字符集通常被稱為Unicode字符集。
如上文所述,Unicode 2.0發布時,Unicode字符集與UCS字符集基本保持了一致,之後雖然二者獨立存在,但是一直在保持互相的兼容。
在ISO與unicode合併之前,ISO就有一套字符編碼模式,也就是UCS-2。
UCS-2的規則就是用兩個字節來表示字符集中的字符,並且它使用的是直接映射的方式。所以可以簡單理解為,UCS-2就是將字符的數字編號直接轉化為二進制,然後用兩個字節來進行存儲。
與ASCII類似,此時的UCS-2其實可以視為一套字符集,也可以視為一套編碼規則。
UCS-2用兩個字節來表示一個字符,所能容納的字符數量為2^16 = 65536個。
在ISO與Unicode合併字符集之後,雙方約定字符集需要容納的字符數量遠遠超過65535個(到目前為止,Unicode字符集可容納的字符量為2^16 * 17 = 1114112個),此時UCS-2顯然不夠用了,所以ISO推出了新的規則,就是UCS-4.
UCS-4與UCS-2基本一樣,唯一的不同點是,UCS-4使用4個字節來表示一個字符。
同樣,UCS-4可以認為是一套字符集,也可以認為是一套編碼規則。
在有些文章里,UCS-4有廣義和狹義兩種含義,廣義上UCS-4包含UCS-2,狹義上不包含。個人理解,在指代字符集的時候,UCS-4包含UCS-2,但是在指代編碼規則時,UCS-4不包含UCS-2。
UCS-2全稱2-byte Universal Character Set,直譯為2字節通用字符集。
UCS-4全稱4-byte Universal Character Set,直譯為4字節通用字符集。
注意:UCS-2和UCS-4組成的UCS字符集,都可以採用UTF-8、UTF-16、UTF-32進行編碼。所以UCS-2與UTF-16並不等同,UCS-4與UTF-32也不等同。
如上文所述,ISO與Unicode合併之後,ISO推出了UCS-4。但是Unicode推出的卻是另外一套編碼規則:UTF-16.
UTF-16源於UCS-2,但是與UCS-2不太一樣。UCS-2屬於定長編碼方式,永遠使用兩個字節來表示一個字符。而UTF-16屬於變長編碼方式,對於UCS-2字符集中的字符(0x0000~0xFFFF)使用2個字節來表示,對於UCS-4字符集中除開UCS-2里的字符(0x10000~0x10FFFFF),使用4個字節來表示。
UTF-16的編碼規則屬於間接映射。對於UCS-2字符集裡面的內容,保持字符編號與生成的編碼串相同,但是對於UCS-4中的其他字符(指除開UCS-2中的字符),字符編號與最終的編碼串並不相同。這裡採取了一套計算算法:代理機制。不過本文對此不做深究。
雖然UTF-16能夠滿足需求,但是一來對於ASCII字符集中的字符,UTF-16仍然需要使用兩個字節來存儲(這樣會有一個字節的空間被浪費),並且ASCII中的字符,其UTF-16編碼的第一個字節將永遠是0x00,而C語言中又因為會將此字節視為字符串末尾導致字符串無法正常解析。所以UTF-16剛推出的時候,就受到了很多的抵制。
由此,UTF-8出現了。
UTF-8也是一種變長編碼方式,它使用1到4個字節來表示一個字符。
字符編號為0~127(十進制)的字符,使用一個字節進行表示。
字符編號為128~2047(十進制)的字符,使用兩個字節進行表示。
字符編號為2048~65535(十進制)的字符,使用三個字節進行表示。
字符編號為65536~2097151(十進制)的字符,使用四個字節進行表示。
UTF-8和UTF-16,都屬於間接映射。也就是說,字符編號與最終的編碼並不完全是一樣的。
實際上,UTF-8的編碼規則如下:
還是上文中的例子,Unicode字符集中的“漢”字,字符編號以16進制表示為“0x6C49”,換算成十進制就是27721,所以需要使用三個字節進行表示。而“0x6C49”換算成二進制就是“110110001001001”,代入上圖中三字節的編碼規則(“1110xxx 10xxxxxx 10xxxxxx”),最終得到的就是”1110110 10110001 10001001″。
當然,對於ASCII字符集裡面的字符(字符編號小於128),UTF-8隻需要一個字節即可表示。與UTF-16的兩個字節相比,空間利用率更高(同樣,在進行數據傳輸時,效率也更高)。
也因此,UTF-8對於ASCII碼屬於完美兼容,而UTF-16隻能算是間接兼容(畢竟多了一個字節,解析的時候還需要進行轉化)。考慮到計算機世界裡ASCII字符的廣泛性,這一點意義重大。
順便說一句,雖然上面並沒有介紹UTF-16的代理機制,但是可以說明的是,這個代理機制的算法要比UTF-8的算法更加複雜,一定程度上也導致了UTF-16進行編碼和解碼需要耗費更多的資源。
此外,可以看到,UTF-8編碼產出的字節,都帶有固定的前綴。這樣做有幾個好處:
第一,字符使用UTF-8編碼之後,第一個字節的前面的幾位,可以明確標識出來,此字符需要幾個字節才能表示出來。這樣的話,解碼程序在讀入每一個字節的時候,就能夠知道當前字節是否為一個字符的首字節;如果是首字節的話,立刻就能知道還需要讀入幾個字節才能解析出來這個字符。
第二,字符經UTF-8編碼之後,生成做到多個字節中,第一個字節的固定前綴與後續字節的固定前綴都不一樣。這樣就保證,在傳輸過程中,如果出現了局部的字節錯誤,比如增加、丟失、修改了某些字節。將只會影響到有限個字符,並不會導致後續的所有的字符都解析錯誤。這一點是UTF-16、UTF-32、GB系列都做不到的事情。
第三,同樣因為編碼後,首字節的前綴與後續字節的前綴都不同,所以從UTF-8字節流中的任一字節開始,往後或者往前都可以很輕易的找到當前字符或者臨近字符的起始位置。
第四,依照目前的規則(檢查首字節,在第一個0出現之前,有幾個1,就代表當前字符需要用多少個字節進行表示),UTF-8可以很輕易地擴展到5個字節、6個字節,甚至是7個字節和8個字節。這就保證了UTF-8可以很輕易地支持Unicode字符集的不斷擴充。
與UTF-8和UTF-16相比,UTF-32就比較簡單了。
UTF-32的編碼規則屬於直接映射,並且每個字符都使用四個字節來表示。
因此,UTF-32比UTF-16更浪費空間。但是因為使用的是定長編碼(每個字符都是四個字節),所以文本處理速度上要比UTF-8和UTF-16快一些。
在三大UTF編碼中,UTF-32既不是最早出現的(UTF-16),也不是最優設計(目前公認UTF-8為最優設計),所以目前已經很少有地方在用了。
上文聊到一個內容,UTF-16編碼,有可能使用兩個或者四個字節來表示一個字符。那麼問題來了,假設存在一個字符,其用UTF-16編碼之後,對應的字節流,用16進制表示為0xFA 0xFB。這時候,在計算機存儲與傳輸中,到底應該是0xFA放前面呢,還是應該0xFB放前面呢?
比較遺憾的是,在計算機發展歷程中,出於各種各樣的原因,大家並沒有形成統一,而是出現了多種方案,比較常見的是如下兩種:
一、大端序(Big-Endian):又稱高尾端序,即數據的尾端存儲在內存的高地址;數據的頭端存儲在內存的低地址。
二、小端序(Little-Endian):又稱低尾端序,即數據的尾端存儲在內存的低地址;數據的頭端存儲在內存的高地址。
為了方便理解記憶,這裡用幾個例子來對大端序和小端序進行下簡單的說明。
首先,我們在閱讀和書寫二進制串時,總是高位在前,低位在後。比如,拿“漢字”為例,其中“漢”對應的unicode編碼為“U+6C49”,“字”對應的unicode編碼為“U+5B57”,如下所示:
而計算機內存的地址增長,我們設定為從左到右,如下圖所示:
那麼這種情況下,大端序,就是將寫入內存時,字節順序不變。如下所示:
而小端序,就需要將字節串前後顛倒一下順序,再寫入內存,如下所示:
注意:
不過,問題來了,上面舉的例子中,“漢”和“字”在UTF-16編碼下,都只需要兩個字節就能表示。那對於需要四個字節才能表示的字符呢?這裡選取兩個字符,對應的unicode編碼分別為”U+129024″( )與“U+4E00”( )。其中第一個字符使用UTF-16進行編碼時需要做間接映射,需要用4個字節來表示,而第二個字節做直接映射即可。如下:
此時,在兩種字節順序中的表現如下:
大端序:
小端序:
可以看到,在UTF-16中,即使對於需要使用四個字節來表示的字符,大端序和小端序的作用範圍還是被限制到了兩個字節。
實際上,這裡有一個碼元(code unit)的概念。
在解釋碼元之前,需要先解釋另外一個概念:CES。
CES,全稱Character Encoding Scheme,可以直譯為字符編碼模式,是指將字節流轉換為最終的bit流的規則。
而上文中,提到過兩個相關的概念:CCS(編號字符集)和CEF(字符編碼方式)。
CCS(Coded Character Set):編號字符集,指帶有數字編號的字符集合。
CEF(Character Encoding Form):字符編碼方式,將字符集的數字編號轉換為字節流的規則。
三者之間的關係如下:
舉個例子(為了方便閱讀,最終的bit流以16進制的方式展示):
其中,CEF得出的字節流可以理解為數字編號在計算機中邏輯表示方式,我們前面介紹到的UTF-8、UTF-16都是CEF;而CES的得出bit流序列可以理解為數字編號在計算機中的物理表現方式,上面提到的字節序(大端序、小端序等),就可以認為是字符編碼中的CES。
回到碼元的概念。碼元,可以認為是CEF在將字節流轉變為bit流時的最小操作單元。
舉個例子,UTF-16中,以2個字節為一個碼元,所以在生成bit流時,只會在2個字節內執行大端序和小端序的排序規則。
類似的,在UTF-32中,以4個字節為一個碼元。但是,在UTF-8中,以1個字節作為一個碼元,所以在使用UTF-8進行編碼時,大端序和小端序其實並不會起作用。
由於在使用諸如UTF-16或者UTF-32等以多個字節作為一個碼元的編碼方式時,對於同一個bit串,使用大端序和小端序解析出來的最終結果很有可能完全不同。所以,在進行數據傳輸時,數據的生產方必須告知接收方應該使用哪種方式進行解析。而這個告知操作便由BOM(Byte-Order Mark)來實現。
在Unicode中,有一個字符,其編碼為U+FEFF,其含義為零寬度不中斷空格(ZERO WIDTH NO-BREAK SPACE)。它名義上是個空格,但是寬度為0,所以不可見,也無法被打印出來,換句話說,這個字符其實沒啥用。
但是BOM便是藉助於這個字符來實現。
為了告知字節流的接收方,這串bit的字節順序是什麼樣子的,約定了個辦法。就是在每串字節流前面,都要添加一個上述的字符U+FEFF。對於UTF-16如果是大端序,首先讀出來的兩個字節就會是0xFE 0xFF;如果是小端序,首先讀出來的兩個字節就會是0xFF 0xFE。這個強行加載字節流最前面,用來表示字節序的字符,就是上文所說的BOM。類似的,對於UTF-32,如果是大端序,首先讀出來的就是0x00 0x00 0xFE 0xFF,而如果是小端序,首先讀出來的就是0xFF 0xFE 0x00 0x00.
從Unicode 3.2開始,U+FEFF這個字符被規定只能出現在字節流的開頭,且只能用於標識字節序,所以這個字符又有了個別名:字節序標記。不過Unicode又添加了個字符用於標識零寬度不中斷空格,編碼為U+2060。
上文也提到過,對於UTF-8來說,不存在字節序所帶來的問題,所以,UTF-8產出的字節流是根本不需要BOM的。不過某些時候,還是會給UTF-8的字節流添加一個BOM注意此時並不是為了標識當前的字節序,而是表示當前字節流是用UTF-8編碼完成的(畢竟UTF-8根本沒有字節序問題需要BOM解決)。而在UTF-8前面添加的這個BOM,對應的字節流是0xEF 0xBB 0xBF。
對BOM做下簡單的整理,如下:
現在,回到文章最初時提的兩個問題:
Q:為什麼同一個字符串,使用GBK和UTF-8進行編碼後的字節數不一樣?
A:因為GBK對於一個字符,恆定使用兩個字節來表示,但是UTF-8會使用1~4個字節來表示。而文章開頭時,給出的示例字符串為三個漢字“哈哈哈”,在UTF-8中,一個漢字會用三個字節來表示。所以gbk編碼後,字節數為2 * 3 = 6,而UTF-8編碼後,字節數為3 * 3 = 9.
Q:為什麼在獲取字節數時,不指定charset的結果與指定使用UTF-8時相同?
A:可以看一下getByte()的源碼,如下:
繼續看958行的encode方法:
注意看384行,會取默認的charset,繼續跟下去:
看608行,取得時系統屬性file.encoding,以此作為默認的編碼方式。
驗證一下,如下:
CCS、CES、CEF、碼元的概念,皆引用自知乎專欄( ),不保證正確性與通用性,不過個人認為這幾個概念,對於理解unicode、UTF等有着極大的幫助。
java的String.getBytes()方法,編碼問題
要先知道fileName原先的編碼,才好清楚用哪個編碼來取得byte[]。
如果不知道原來的編碼、又用錯了編碼的參數,就只有亂碼了。
java的數字與字符的強轉 ,究竟按照那種編碼方式來轉跟什麼有關係?
文件編碼是給你和IDE看的 編譯後就不存在這問題。 java運行時的編碼說白了就是固定的編碼,和運行環境掛鈎,不同環境解釋出來不同的字符。控制台所支持的編碼裡面 4 那一位有可能不是什方片。 和運行環境有關。
給你摘抄一段:
– 編譯:我們用javac編譯JAVA文件時,javac不會智能到猜出你所要編譯的文件是什麼編碼類型的,所以它需要指定讀取文件所用的編碼類型。默認 javac使用平台缺省的字符編碼類型來解析JAVA文件。平台缺省編碼是操作系統決定的,我們使用的是中文操作系統,語言區域設置通常都是中國大陸,所 以平台缺省編碼類型通常是GBK。這個編碼類型我們可以在JAVA中使用System.getProperty(“file.encoding”)來查 看。所以javac會默認使用GBK來解析JAVA文件。如果我們要改變javac所用的編碼類型,就要加上-encoding參數,如javac -encoding utf-8 Test.java。
這裡要另外提一下的是eclipse使用的是內置的編譯器,並不能添加參數,如果要為javac添加參數則建議使用ANT來編譯。不過這並非出現亂碼的塬因,因為eclipse可以為每個JAVA文件設置字符編碼類型,而內置編譯器會根據此設置來編譯JAVA文件。
運行:編譯後字符數據會以UNICODE格式存入字節碼文件中。然後eclipse會調用java命令來運行此字節碼文件。因為字節碼中的字符總是 UNICODE格式,所以java讀取字節碼文件並沒有編碼轉換過程。虛擬機讀取文件後,字符數據便以UNICODE格式存儲在內存中了。
JAVA字符編碼問題
這種編碼問題真是很tricky的問題。說它tricky是因為這至少涉及到以下4種編碼選取的排列組合(有時甚至更多),更有時乃至會發生錯進錯出,負負得正,中間過程錯了但反而到不是亂碼的情況。
(1)源代碼的編碼
(2)編譯時告訴java編譯器的源代碼編碼
(3)運行時jvm參數file.encoding
(4)輸出終端對輸出字節流的解碼所採用的碼組
在這簡單情況下(1)和(2)一致,(3)和(4)一致就不會因為編解碼映射錯誤(當然字符向終端字體映射的錯誤是另一回事,如字體缺失之類)。而(1)(2)和(3)(4)不必一致,這樣就使得不必強求開發編譯環境和運行應用環境的編碼必須一致。
源代碼的錄入與編譯若在在一個平台上時,大多數情況沒有問題(反而用聰明的Idea IDE設置錯誤時會亂套,越是簡陋的開發環境越不太會錯)。但是如果你在中文GBK編碼平台上的源代碼在別人的unicode編碼平台上編譯,就有問題了。所以和別人,特別是和不同母語的人合作編程時,建議要麼約定一律用unicode作為源文件編碼;要麼只用ASCII字符,反正其他編碼一般都和ASCII兼容的,對於非ASCII字符,用Java的/uxxxx表示機制,比如”中國”就表示為”\u4e2d\u56fd”。4e2d和56fd分別是中國二字的unicode十六進制編碼。
但我認為樓主在這裡其實主要關心的是運行時的編碼一致問題,即(3)和(4)。所以言歸正傳,讓我們來檢查它們是否一致。
由於正如上述,iso8859-1編碼集其實是被其他所有公認的編碼集所兼容的,也就是說它是所有公認編碼集的公共子集。所以以iso8859-1為基礎可以外延到任何一個公認編碼集。事實上大多數情況也是這樣做的。比如java System property里設定了encoding為iso8859-1,事實上不僅僅是一個Latin字母的映射,在非Latin區域按JVM宿主操作系統的編碼擴展。即選iso8859-1其實是選擇了宿主操作系統的默認編碼。
假設樓主的操作系統編碼是GBK,那麼file.encoding=iso8859-1相當於選擇了file.encoding=GBK。那麼System.out.println(…)這個核心類方法會將china字符轉換為file.encoding指定的編碼(GBK)字節由out流輸出給最終out所綁定的終端。比如console一般採用系統默認編碼也是GBK的話,那就和file.encoding一致,能正常解碼,不會亂碼。
至於System.out.write()直接寫字節流。由於該字節流是由china.getBytes()得到的,在不指定編碼的時候使用file.encoding指定的默認值的(即GBK),因此Str-Byte的編碼方法GBK和console採用的解碼方法GBK又是一致的,所以也不是亂碼。
但是這時候用toHexString打印出的兩個字節串是不一樣的。先直接把china逐字強行轉換為int的情況,不涉及輸出編碼,總是unicode的。(JVM規範規定class里字串必須unicode編碼)只要上述(1) (2)匹配,java編譯器會自動從各種編碼的源文件正確轉成class文件里統一unicode編碼的字串。相反,作為一個題外話提一下,當(1)(2)不匹配時會在特定的一種配合(1)(2)的(3)(4)也不匹配的情況下會負負得正輸出正常,但這是絕對錯誤的做法,因為任何要求(1)(2)和(3)(4)有匹配關係的要求都是在應用中可能無法滿足的。java編譯器對這種情況也會報告warning,但不fail。
綜上,一旦file.encoding設成宿主操作系統默認而系統consle也採用操作系統默認編解碼的話,(3)(4)總是一致的,無論系統選擇的是GBK還是utf-8等等。
那麼如果file.encoding不選系統默認呢?比如utf-8。那就很可能出現亂碼了。但是,慢着,試驗的結果還是沒有亂碼。那是因為file.encoding是靜態的JVM系統參數,在程序里像樓主那樣設定是不起作用的(我不知道有沒有辦法發一個什麼通知讓這種程序改變生效的)。必須作為JVM參數直接傳給java程序讓它構造虛擬機的時候就得到這個參數,否則JVM會去拿宿主系統的默認值,就相當於又回到設file.encoding=iso8859-1了。
java -Dfile.encoding=utf-8 A
這下終於亂碼了,而且兩個都亂了。打印出的字節串一個還是unicode,另一個從GBK變到utf-8了。
如果你發現試驗的現象和我上面說的正好相反,請注意檢查console的編碼設置,我們上面假設它也採用了宿主系統默認編碼,但有些console很高級的嘞,可以設置成不通編碼的(其實幾乎所有的都可以)。那麼分析的方法和上面一樣,結果可能正好相反。
java編程題 字符編碼aaaaaaabbbbbcerrrrggggggggsssssspoq
String str = “aaaaaaabbbbbcerrrrggggggggsssssspoq”;
while(str.contains(“qqq”)){
str = str.replace(“qqq”,”a7b5cer4g”);
}
原創文章,作者:REJUO,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/315937.html