1. 程式人生 > >深度剖析java編碼

深度剖析java編碼

 理解:

             1,Java編譯器(即編譯成class檔案時) 用的是unicode字符集。

             2,亂碼主要是由於不同的字符集相互轉換導致的,理論上各個字元的編碼規則是不同的,是不能相互轉換的,所以根本解決亂碼的方法就是不要轉換編碼方式,編碼方式前 後統一。

            3,ASCII、GB2312、GBK、GB18030、Big5、Unicode都是字符集的名稱。

它們定義了採用1~2個位元組的編碼規範,為每個字元賦予了一個獨一無二的編號。這個編號就是我們所說的“字元編碼”。

              4, Unicode字符集定義的字元編碼並不適合直接通過網路傳輸表達,因為它們必須轉換成像0101這樣的二進位制位元組流傳輸。所以就出現了不同的轉換規範實現方式:UTF-8,TF-16等。這些不同的轉換規範轉換後的編碼值和Unicode是不同的.(unicode是字符集,編碼實現是utf-8,utf-16等,unicode到utf-8是有演算法的,有統一的規則,但unicode和gbk等其他的編碼方式是沒有直接聯絡的不能轉換)。

             5,不要輕易地使用或濫用String類的getBytes(encoding)方法,更要儘量避免使用getBytes()方法。因為這個方法是平臺依賴的,在平臺不可預知的情況下完全可能得到不同的結果。如果一定要進行位元組編碼,則使用者要確保encoding的方法就是當初字串輸入時的encoding(即知道以前的編碼)。

            6, 

http://825635381.iteye.com/blog/2087380(java 預設的Unicode和外部資源編碼的理解)

              new String(input.getBytes("ISO-8859-1"), "GB18030")          
             上面這段程式碼代表什麼?有人會說: “把input字串從ISO-8859-1編碼方式轉換成GB18030編碼方式”。      
              如果這種說法正確,那麼又如何解釋我們剛提到的java字串都採用unicode編碼呢? 
     這種說法不僅是欠妥的,而且是大錯特錯的,讓我們一一來分析,其實事實是這樣的:我們本應該用       
     GB18030的編碼來讀取資料並解碼成字串,但結果卻採用了ISO-8859-1的編碼,導致生成一個錯誤的字       
       符串。要恢復,就要先把字串恢復成原始位元組陣列,然後通過正確的編碼GB18030再次解碼成字串(即     
        把以GB18030編碼的資料轉成unicode的字串)。注意,字串永遠都是unicode編碼的。          
        但編碼轉換並不是負負得正那麼簡單,這裡我們之所以可以正確地轉換回來,是因為 ISO8859-1 是單位元組編   
        碼,所以每個位元組被按照原樣 轉換為 String ,也就是說,雖然這是一個錯誤的轉換,但編碼沒有改變,所以      
        我們仍然有機會把編碼轉換回來!      
          總結: 
         所以,我們在處理java的編碼問題時,要分清楚三個概念:Java採用的編碼:unicode,JVM平臺預設字元 集和外部資源的編碼。 

          7,假如我們需要從磁碟檔案、資料庫記錄、網路傳輸一些字元,儲存到Java的變數中,要經歷由bytes-->encode字元-->Unicode字元的轉換(例如new String(bytes, encode));而要把Java變數儲存到檔案、資料庫或者通過網路傳輸,系統要做一個Unicode字元-->encode字元-->bytes的轉換(例如String.getBytes([encode]))   

 

       8,前面提到從ASCII、GB2312、GBK到GB18030的編碼方法是向下相容的。而Unicode只與ASCII相容(更準確地說,是與ISO-8859-1相容),與GB碼不相容。例如“漢”字的Unicode編碼是6C49,而GB碼是BABA。(簡單來說有兩大陣營ANSI和unicode不能相互轉換除了ISO-8859-1,因為他有單位元組的特殊性)
      
      9,  String name = "我來了";

             String str = new String(name.getBytes("x"),"xxx");

             不管jvm這裡預設的編碼是GBK還是UTF-8還是其他編碼,name.getBytes("x")java都會自動幫你轉成Unicode編碼,然後再以x的編碼方式轉成xxx的編碼字符集。

       例如:如何GbK轉成UTF-8,真正的核心問題是GBK怎麼轉成unicode(無法直接轉只能找GBK和unicode對照表),轉成unicode以後轉utf-8就好轉了(因為有規律)。java特殊因為java 字串都是預設unicode的(生產的class檔案都是 unicode字符集),所以無論你name.getBytes("xx")是什麼編碼得到的unicode值都是unicode字符集的正確值。(既然java字元預設都轉成了unicode那麼為什麼GBK轉UTF-8為什麼還是亂碼?java都做到預設轉unicode編碼了可以實現不亂碼了,不知內部為什麼?),所有有人在java語言中也有實現GBK轉UTF-8,其實就是直接unicode轉utf-8。

 

 還有一篇連載部落格非常好 【Java基礎專題】編碼與亂碼(08)---JavaEE防止中文亂碼的設定

  以下是編碼和字符集的基礎知識1:轉自http://www.cnblogs.com/xiaomia/archive/2010/11/28/1890072.html

以下是編碼和字符集的基礎知識2:轉自http://polaris.blog.51cto.com/1146394/377468/

       每一個程式設計師都不可避免的遇到字元編碼的問題,特別是做Web開發的程式設計師,“亂碼問題”一直是讓人頭疼的問題,也許您已經很少遇到“亂碼”問題,然而,對解決亂碼的方法的內在原理,您是否明白?本人作為一個程式設計師,在字元編碼方面同樣遇到不少問題,而且一直對各種編碼懵懵懂懂、不清不楚;在工作中也曾經遇到一個很煩人的編碼問題。這兩天在網上收集了大量編碼方面的資料,對字元編碼算是理解的比較清楚了。下面把我認為比較重要的知識點記錄下來,一方面方便以後複習;另一方面也希望給跟我一樣懵懵懂懂的人一個參考。不對或不妥之處,請批評指正。

在此之前,先了解一些有用概念:“字符集”、“字元編碼”和“內碼”。 

1、字符集與字元編碼

字元是各種文字和符號的總稱,包括各個國家文字、標點符號、圖形符號、數字等。字符集是多個字元的集合,字符集種類較多,每個字符集包含的字元個數不同,常見字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。計算機要準確的處理各種字符集文字,需要進行字元編碼,以便計算機能夠識別和儲存各種文字。 

編碼(encoding)和字符集不同。字符集只是字元的集合,不一定適合作網路傳送、處理,有時須經編碼(encode)後才能應用。如Unicode可依不同需要以UTF-8、UTF-16、UTF-32等方式編碼。

字元編碼就是以二進位制的數字來對應字符集的字元。

因此,對字元進行編碼,是資訊交流的技術基礎。

使用哪些字元。也就是說哪些漢字,字母和符號會被收入標準中。所包含“字元”的集合就叫做“字符集”。

規定每個“字元”分別用一個位元組還是多個位元組儲存,用哪些位元組來儲存,這個規定就叫做“編碼”。

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

注意:Unicode字符集有多種編碼方式,如UTF-8、UTF-16等;ASCII只有一種;大多數MBCS(包括GB2312)也只有一種。

 

2、什麼是內碼?

 

2.1 維基百科的解釋

 

在電腦科學及相關領域當中,內碼指的是“將資訊編碼後,透過某種方式儲存在特定記憶裝置時,裝置內部的編碼形式”。在不同的系統中,會有不同的內碼。

在以往的英文系統中,內碼為ASCII。在繁體中文系統中,目前常用的內碼為大五碼(Big5)。在簡體中文系統中,內碼則為國標碼(國家標準程式碼:現在強制要求使用GB18030標準;較舊計算機仍然使用GB2312)。而統一碼(Unicode)則為另一常見內碼。

 

2.2 百度百科的解釋

 

內碼是指整機系統中使用的二進位制字元編碼,是溝通輸入、輸出與系統平臺之間的交換碼,通過內碼可以達到通用和高效率傳輸文字的目的。比如MS Word中所儲存和呼叫的就是內碼而非圖形文字。英文ASCII字符采用一個位元組的內碼錶示,中文字元如國標字符集中,GB2312、GB12345、GB13000皆用雙位元組內碼,GB18030(27,533漢字)雙位元組內碼漢字為20,902個,其餘6,631個漢字用四位元組內碼。

 

3、字元編碼分類總結

 

下面從計算機對多國語言支援的角度來總結字元編碼。

 

3.1 ASCII編碼

 

以下來自“維基百科”:

 

ASCII(American Standard Code for Information Interchange,美國資訊互換標準程式碼)是基於拉丁字母的一套電腦編碼系統。它主要用於顯示現代英語,而其擴充套件版本EASCII則可以勉強顯示其他西歐語言。它是現今最通用的單位元組編碼系統(但是有被UniCode追上的跡象),並等同於國際標準ISO/IEC 646。

 

ASCII第一次以規範標準的型態發表是在1967年,最後一次更新則是在1986年,至今為止共定義了128個字元;其中33個字元無法顯示(這是以現今作業系統為依歸,但在DOS模式下可顯示出一些諸如笑臉、撲克牌花式等8-bit符號),且這33個字元多數都已是陳廢的控制字元。控制字元的用途主要是用來操控已經處理過的文字。在33個字元之外的是95個可顯示的字元,包含用鍵盤敲下空白鍵所產生的空白字元也算1個可顯示字元(顯示為空白)。

 

ASCII表:見http://zh.wikipedia.org/zh-cn/ASCII

 

ASCII缺點:

 

ASCII的最大缺點是隻能顯示26個基本拉丁字母、阿拉伯數目字和英式標點符號,因此只能用於顯示現代美國英語(而且在處理英語當中的外來詞如naïve、café、élite等等時,所有重音符號都不得不去掉,即使這樣做會違反拼寫規則)。而EASCII雖然解決了部份西歐語言的顯示問題,但對更多其他語言依然無能為力。因此現在的蘋果電腦已經拋棄ASCII而轉用Unicode。

 

最早的英文DOS作業系統的系統內碼是:ASCII。計算機這時候只支援英語,其他語言不能夠在計算機儲存和顯示。

 

在該階段,單位元組字串使用一個位元組存放一個字元(SBCS,Single Byte Character System)。如:"Bob123"佔6個位元組。

 

3.2 ANSI編碼

 

為使計算機支援更多語言,通常使用0x800~xFF範圍的2個位元組來表示1個字元。比如:漢字 '中' 在中文作業系統中,使用 [0xD6,0xD0]這兩個位元組儲存。

 

不同的國家和地區制定了不同的標準,由此產生了GB2312,BIG5,JIS等各自的編碼標準。這些使用2個位元組來代表一個字元的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文系統下,ANSI 編碼代表 GB2312 編碼,在日文作業系統下,ANSI 編碼代表 JIS 編碼。

 

不同 ANSI 編碼之間互不相容,當資訊在國際間交流時,無法將屬於兩種語言的文字,儲存在同一段 ANSI 編碼的文字中。

 

中文DOS、中文/日文Windows 95/98時代系統內碼使用的是ANSI編碼(本地化)

 

在使用ANSI編碼支援多語言階段,每個字元使用一個位元組或多個位元組來表示(MBCS,Multi-Byte Character System),因此,這種方式存放的字元也被稱作多位元組字元。比如,"中文123" 在中文 Windows 95 記憶體中為7個位元組,每個漢字佔2個位元組,每個英文和數字字元佔1個位元組。

 

在非 Unicode 環境下,由於不同國家和地區採用的字符集不一致,很可能出現無法正常顯示所有字元的情況。微軟公司使用了內碼表(Codepage)轉換表的技術來過渡性的部分解決這一問題,即通過指定的轉換表將非 Unicode 的字元編碼轉換為同一字元對應的系統內部使用的 Unicode 編碼。可以在“語言與區域設定”中選擇一個內碼表作為非 Unicode 編碼所採用的預設編碼方式,如936為簡體中文GBK,950為正體中文Big5(皆指PC上使用的)。在這種情況下,一些非英語的歐洲語言編寫的軟體和文件很可能出現亂碼。而將內碼表設定為相應語言中文處理又會出現問題,這一情況無法避免。從根本上說,完全採用統一編碼才是解決之道,但目前尚無法做到這一點。

 

  內碼表技術現在廣泛為各種平臺所採用。UTF-7 的內碼表是65000,UTF-8 的內碼表是65001。

 

3.3 Unicode編碼

 

為了使國際間資訊交流更加方便,國際組織制定了 UNICODE 字符集,為各種語言中的每一個字元設定了統一併且唯一的數字編號,以滿足跨語言、跨平臺進行文字轉換、處理的要求。

 

Unicode字符集可以簡寫為UCS(Unicode Character Set)。早期的unicodeUnicode標準有UCS-2、UCS-4的說法。UCS-2用兩個位元組編碼,UCS-4用4個位元組編碼。

 

在 UNICODE 被採用之後,計算機存放字串時,改為存放每個字元在 UNICODE 字符集中的序號。目前計算機一般使用 2 個位元組(16 位)來存放一個序號(DBCS,Double Byte Character System),因此,這種方式存放的字元也被稱作寬位元組字元。比如,字串 "中文123" 在 Windows 2000 下,記憶體中實際存放的是 5 個序號,一共10個位元組。

 

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

 

4、常用編碼規則

 

4.1 單位元組字元編碼

 

(1)編碼標準:ISO-8859-1。

 

(2)說明:最簡單的編碼規則,每一個位元組直接作為一個 UNICODE 字元。比如,[0xD6, 0xD0] 這兩個位元組,通過 iso-8859-1 轉化為字串時,將直接得到 [0x00D6, 0x00D0] 兩個 UNICODE 字元,即 "ÖÐ"。

 

反之,將 UNICODE 字串通過 iso-8859-1 轉化為位元組串時,只能正常轉化 0~255 範圍的字元。

 

4.2 ANSI編碼

 

(1)GB2312, BIG5, Shift_JIS, ISO-8859-2。

 

(2)把 UNICODE 字串通過 ANSI 編碼轉化為“位元組串”時,根據各自編碼的規定,一個 UNICODE 字元可能轉化成一個位元組或多個位元組。

 

反之,將位元組串轉化成字串時,也可能多個位元組轉化成一個字元。比如,[0xD6, 0xD0] 這兩個位元組,通過 GB2312 轉化為字串時,將得到 [0x4E2D] 一個字元,即 '中' 字。

 

“ANSI 編碼”的特點:

 

(1)這些“ANSI 編碼標準”都只能處理各自語言範圍之內的 UNICODE 字元。

 

(2)“UNICODE 字元”與“轉換出來的位元組”之間的關係是人為規定的。

 

4.3 UNICODE編碼

 

(1)編碼標準:UTF-8, UTF-16, UnicodeBig。

 

(2)與“ANSI 編碼”類似的,把字串通過 UNICODE 編碼轉化成“位元組串”時,一個 UNICODE 字元可能轉化成一個位元組或多個位元組。

 

與“ANSI 編碼”不同的是:

 

(1)這些“UNICODE 編碼”能夠處理所有的 UNICODE 字元。

 

(2)“UNICODE 字元”與“轉換出來的位元組”之間是可以通過計算得到的。

 

我們實際上沒有必要去深究每一種編碼具體把某一個字元編碼成了哪幾個位元組,我們只需要知道“編碼”的概念就是把“字元”轉化成“位元組”就可以了。對於“UNICODE 編碼”,由於它們是可以通過計算得到的,因此,在特殊的場合,我們可以去了解某一種“UNICODE 編碼”是怎樣的規則。

 

5、編碼的區別

 

5.1 GB2312、GBK和GB18030

 

(1)GB2312 

 

當中國人們得到計算機時,已經沒有可以利用的位元組狀態來表示漢字,況且有6000多個常用漢字需要儲存,於是想到把那些ASCII碼中127號之後的奇異符號們直接取消掉, 規定:一個小於127的字元的意義與原來相同,但兩個大於127的字元連在一起時,就表示一個漢字,前面的一個位元組(稱之為高位元組)從0xA1用到0xF7,後面一個位元組(低位元組)從0xA1到0xFE,這樣我們就可以組合出大約7000多個簡體漢字了。在這些編碼裡,我們還把數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在 ASCII 裡本來就有的數字、標點、字母都統統重新編了兩個位元組長的編碼,這就是常說的"全形"字元,而原來在127號以下的那些就叫"半形"字元了。這種漢字方案叫做 "GB2312"。GB2312 是對 ASCII 的中文擴充套件。相容ASCII。

 

(2)GBK 

 

但是中國的漢字太多了,我們很快就就發現有許多人的人名沒有辦法在這裡打出來,不得不繼續把 GB2312 沒有用到的碼位找出來用上。後來還是不夠用,於是乾脆不再要求低位元組一定是127號之後的內碼,只要第一個位元組是大於127就固定表示這是一個漢字的開始,不管後面跟的是不是擴充套件字符集裡的內容。結果擴充套件之後的編碼方案被稱為 “GBK” 標準,GBK 包括了 GB2312 的所有內容,同時又增加了近20000個新的漢字(包括繁體字)和符號。

 

(3)GB18030 

 

後來少數民族也要用電腦了,於是我們再擴充套件,又加了幾千個新的少數民族的字,GBK 擴成了 GB18030。從此之後,中華民族的文化就可以在計算機時代中傳承了。 

 

中國的程式設計師們看到這一系列漢字編碼的標準是好的,於是通稱他們叫做 "DBCS"(Double Byte Charecter Set 雙位元組字符集)。在DBCS系列標準裡,最大的特點是兩位元組長的漢字字元和一位元組長的英文字元並存於同一套編碼方案裡,因此他們寫的程式為了支援中文處理,必須要注意字串裡的每一個位元組的值,如果這個值是大於127的,那麼就認為一個雙位元組字符集裡的字元出現了。在這種情況下,"一個漢字算兩個英文字元!"。然而,在Unicode環境下卻並非總是如此。 

 

5.1 Unicode和BigEndianUnicode

 

這兩個指示儲存順序不同,如"A"的Unicode編碼為6500,而BigEndianUnicode編碼為0065。

 

5.2 UTF-7、UTF-8和UTF-16

 

在Unicode裡,所有的字元被一視同仁。漢字不再使用“兩個擴充套件ASCII”,而是使用“1個Unicode”,注意,現在的漢字是“一個字元”了,於是,拆字、統計字數這些問題也就自然而然的解決了。

 

但是,這個世界不是理想的,不可能在一夜之間所有的系統都使用Unicode來處理字元,所以Unicode在誕生之日,就必須考慮一個嚴峻的問題:和ASCII字符集之間的不相容問題。 

 

我們知道,ASCII字元是單個位元組的,比如“A”的ASCII是65。而Unicode是雙位元組的,比如“A”的Unicode是0065,這就造成了一個非常大的問題:以前處理ASCII的那套機制不能被用來處理Unicode了。

 

另一個更加嚴重的問題是,C語言使用'\0'作為字串結尾,而Unicode裡恰恰有很多字元都有一個位元組為0,這樣一來,C語言的字串函式將無法正常處理Unicode,除非把世界上所有用C寫的程式以及他們所用的函式庫全部換掉。

 

於是,比Unicode更偉大的東東誕生了,之所以說它更偉大是因為它讓Unicode不再存在於紙上,而是真實的存在於我們大家的電腦中。那就是:UTF。

 

UTF= UCS Transformation Format,即UCS轉換(傳輸)格式。

 

它是將Unicode編碼規則和計算機的實際編碼對應起來的一個規則。現在流行的UTF有2種:UTF-8和UTF-16。

 

這兩種都是Unicode的編碼實現。

 

5.2.1 UTF-8

 

UCS-2編碼(16進位制)   UTF-8 位元組流(二進位制)

 

0000 - 007F         0xxxxxxx

0080 - 07FF         110xxxxx 10xxxxxx

0800 - FFFF         1110xxxx 10xxxxxx 10xxxxxx 

 

例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間,所以肯定要用3位元組模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進位制是:0110 110001 001001,用這個位元流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

 

可見UTF-8是變長的,將Unicode編碼為00000000-0000007F的字元,用單個位元組來表示; 00000080-000007FF的字元用兩個位元組表示;00000800-0000FFFF的字元用3位元組表示。因為目前為止Unicode-16規範沒有指定FFFF以上的字元,所以UTF-8最多是使用3個位元組來表示一個字元。但理論上來說,UTF-8最多需要用6位元組表示一個字元。 

 

UTF-8相容ASCII。

 

5.2.2 UTF-16(標準的Unicode成為UTF-16)

 

UTF-16和上面提到的Unicode本身的編碼規範是一致的。

 

UTF-16以16位為單元對UCS進行編碼。對於小於0x10000的UCS碼,UTF-16編碼就等於UCS碼對應的16位無符號整數。對於不小於0x10000的UCS碼,定義了一個演算法。不過由於實際使用的UCS2,或者UCS4的BMP必然小於0x10000,所以就目前而言,可以認為UTF-16和UCS-2基本相同。但UCS-2只是一個編碼方案,UTF-16卻要用於實際的傳輸,所以就不得不考慮位元組序的問題。

 

UTF-16不相容ASCII。

 

5.2.3 UTF-7

 

UTF-7 (7-位元 Unicode 轉換格式(Unicode Transformation Format,簡寫成 UTF)) 是一種可變長度字元編碼方式,用以將 Unicode 字元以 ASCII 編碼的字元串來呈現,可以應用在電子郵件傳輸之類的應用。

 

UTF-7並非Unicode標準之一。想要詳細瞭解的可以查閱相關資料。

 

6、Unicode與UTF

 

Unicode是記憶體編碼表示方案(是規範),而UTF是如何儲存和傳輸Unicode的方案(是實現)。

 

6.1 UTF的位元組序和BOM

 

6.1.1 位元組序

 

UTF-8以位元組為編碼單元,沒有位元組序的問題。UTF-16以兩個位元組為編碼單元,在解釋一個UTF-16文字前,首先要弄清楚每個編碼單元的位元組序。例如收到一個“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。如果我們收到UTF-16位元組流“594E”,那麼這是“奎”還是“乙”?

 

Unicode規範中推薦的標記位元組順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一個有點小聰明的想法:

 

在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字元,它的編碼是FEFF。而FFFE在UCS中是不存在的字元,所以不應該出現在實際傳輸中。UCS規範建議我們在傳輸位元組流前,先傳輸字元"ZERO WIDTH NO-BREAK SPACE"。

 

這樣如果接收者收到FEFF,就表明這個位元組流是Big-Endian的;如果收到FFFE,就表明這個位元組流是Little-Endian的。因此字元"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。

 

 

 

UTF-8不需要BOM來表明位元組順序,但可以用BOM來表明編碼方式。字元"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB BF開頭的位元組流,就知道這是UTF-8編碼了。

 

 

 

6.1.2 BOM

 

 

 

(1)BOM的來歷 

 

 

 

為了識別 Unicode 檔案,Microsoft 建議所有的 Unicode 檔案應該以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字元開頭。這作為一個“特徵符”或“位元組順序標記(byte-order mark,BOM)”來識別檔案中使用的編碼和位元組順序。

 

 

 

(2)不同的系統對BOM的支援 

 

 

 

因為一些系統或程式不支援BOM,因此帶有BOM的Unicode檔案有時會帶來一些問題。

 

 

 

①JDK1.5以及之前的Reader都不能處理帶有BOM的UTF-8編碼的檔案,解析這種格式的xml檔案時,會丟擲異常:Content is not allowed in prolog。“對於解決方法,之後我會寫篇文章專門討論該問題。”

 

 

 

②Linux/UNIX 並沒有使用 BOM,因為它會破壞現有的 ASCII 檔案的語法約定。

 

 

 

③不同的編輯工具對BOM的處理也各不相同。使用Windows自帶的記事本將檔案儲存為UTF-8編碼的時候,記事本會自動在檔案開頭插入BOM(雖然BOM對UTF-8來說並不是必須的)。而其它很多編輯器用不用BOM是可以選擇的。UTF-8、UTF-16都是如此。

 

 

 

(3)BOM與XML 

 

 

 

XML解析讀取XML文件時,W3C定義了3條規則:

 

 

 

①如果文件中有BOM,就定義了檔案編碼;

 

②如果文件中沒有BOM,就檢視XML宣告中的編碼屬性;

 

③如果上述兩者都沒有,就假定XML文件採用UTF-8編碼。

 

 

 

6.2 決定文字的字符集與編碼

 

 

 

軟體通常有三種途徑來決定文字的字符集和編碼。

 

 

 

(1)對於Unicode文字最標準的途徑是檢測文字最開頭的幾個位元組。如:

 

 

 

開頭位元組        Charset/encoding

 

 EF BB BF    UTF-8

 

 FE FF     UTF-16/UCS-2, little endian(UTF-16LE)

 

 FF FE     UTF-16/UCS-2, big endian(UTF-16BE)

 

 FF FE 00 00  UTF-32/UCS-4, little endian.

 

 00 00 FE FF  UTF-32/UCS-4, big-endia

 

 

 

(2)採取一種比較安全的方式來決定字符集及其編碼,那就是彈出一個對話方塊來請示使用者。

 

 

 

然而MBCS文字(ANSI)沒有這些位於開頭的字符集標記,現在很多軟體儲存文字為Unicode時,可以選擇是否儲存這些位於開頭的字符集標記。因此,軟體不應該依賴於這種途徑。這時,軟體可以採取一種比較安全的方式來決定字符集及其編碼,那就是彈出一個對話方塊來請示使用者。

 

 

 

(3)採取自己“猜”的方法。

 

 

 

如果軟體不想麻煩使用者,或者它不方便向用戶請示,那它只能採取自己“猜”的方法,軟體可以根據整個文字的特徵來猜測它可能屬於哪個charset,這就很可能不準了。使用記事本開啟那個“聯通”檔案就屬於這種情況。(把原本屬於ANSI編碼的檔案當成UTF-8處理,詳細說明見:http://blog.csdn.net/omohe/archive/2007/05/29/1630186.aspx)

 

 

 

6.3 記事本的幾種編碼

 

 

 

(1)ANSI編碼 

 

記事本預設儲存的編碼格式是:ANSI,即本地作業系統預設的內碼,簡體中文一般為GB2312。這個怎麼驗證呢?用記事本儲存後,使用EmEditor、EditPlus和UltraEdit之類的文字編輯器開啟。推薦使用EmEditor,開啟後,在又下角會顯示編碼:GB2312。

 

 

 

(2)Unicode編碼 

 

用記事本另存為時,編碼選擇“Unicode”,用EmEditor開啟該檔案,發現編碼格式是:UTF-16LE+BOM(有簽名)。用十六進位制方式檢視,發現開頭兩位元組為:FF FE。這就是BOM。

 

 

 

(3)Unicode big endian 

 

用記事本另存為時,編碼選擇“Unicode”,用EmEditor開啟該檔案,發現編碼格式是:UTF-16BE+BOM(有簽名)。用十六進位制方式檢視,發現開頭兩位元組為:FE FF。這就是BOM。

 

 

 

(4)UTF-8 

 

用記事本另存為時,編碼選擇“UTF-8”,用EmEditor開啟該檔案,發現編碼格式是:UTF-8(有簽名)。用十六進位制方式檢視,發現開頭三個位元組為:EF BB BF。這就是BOM。

 

7、幾種誤解,以及亂碼產生的原因和解決辦法

 

7.1 誤解一

 

 

 

在將“位元組串”轉化成“UNICODE 字串”時,比如在讀取文字檔案時,或者通過網路傳輸文字時,容易將“位元組串”簡單地作為單位元組字串,採用每“一個位元組”就是“一個字元”的方法進行轉化。

 

 

 

而實際上,在非英文的環境中,應該將“位元組串”作為 ANSI 字串,採用適當的編碼來得到 UNICODE 字串,有可能“多個位元組”才能得到“一個字元”。

 

 

 

通常,一直在英文環境下做開發的程式設計師們,容易有這種誤解。

 

 

 

7.2 誤解二

 

 

 

在 DOS,Windows 98 等非 UNICODE 環境下,字串都是以 ANSI 編碼的位元組形式存在的。這種以位元組形式存在的字串,必須知道是哪種編碼才能被正確地使用。這使我們形成了一個慣性思維:“字串的編碼”。

 

 

 

當 UNICODE 被支援後,Java 中的 String 是以字元的“序號”來儲存的,不是以“某種編碼的位元組”來儲存的,因此已經不存在“字串的編碼”這個概念了。只有在“字串”與“位元組串”轉化時,或者,將一個“位元組串”當成一個 ANSI 字串時,才有編碼的概念。

 

 

 

不少的人都有這個誤解。

 

 

 

7.3 分析與解決

 

 

 

第一種誤解,往往是導致亂碼產生的原因。第二種誤解,往往導致本來容易糾正的亂碼問題變得更復雜。

 

 

 

在這裡,我們可以看到,其中所講的“誤解一”,即採用每“一個位元組”就是“一個字元”的轉化方法,實際上也就等同於採用 iso-8859-1 進行轉化。因此,我們常常使用 bytes = string.getBytes("iso-8859-1") 來進行逆向操作,得到原始的“位元組串”。然後再使用正確的 ANSI 編碼,比如 string = new String(bytes, "GB2312"),來得到正確的“UNICODE 字串”。