每個程序員都不可避免的遇到字符編碼的問題,特別是作Web開發的程序員,「亂碼問題」一直是讓人頭疼的問題,也許您已經不多遇到「亂碼」問題, 然而,對解決亂碼的方法的內在原理,您是否明白?本人做爲一個程序員,在字符編碼方面一樣遇到很多問題,並且一直對各類編碼懵懵懂懂、不清不楚;在工做中也曾經遇到一個很煩人的編碼問題。這兩天在網上收集了大量編碼方面的資料,對字符編碼算是理解的比較清楚了。下面把我認爲比較重要的知識點記錄下來,一方面方便之後複習;另外一方面也但願給跟我同樣懵懵懂懂的人一個參考。不對或不妥之處,請批評指正。html
在此以前,先了解一些有用概念:「字符集」、「字符編碼」和「內碼」。程序員
字符是各類文字和符號的總稱,包括各個國家文字、標點符號、圖形符號、數字等。字符集是多個字符的集合,字符集種類較多,每一個字符集包含的字符 個數不一樣,常見字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。計算機要準確的處理各類字符集文字,須要進行字符 編碼,以便計算機可以識別和存儲各類文字。 算法
編碼(encoding)和字符集不一樣。字符集只是字符的集合,不必定適合做網絡傳送、處理,有時須經編碼(encode)後才能應用。如Unicode可依不一樣須要以UTF-八、UTF-1六、UTF-32等方式編碼。安全
字符編碼就是以二進制的數字來對應字符集的字符。網絡
所以,對字符進行編碼,是信息交流的技術基礎。編輯器
使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含「字符」的集合就叫作「字符集」。函數
規定每一個「字符」分別用一個字節仍是多個字節存儲,用哪些字節來存儲,這個規定就叫作「編碼」。工具
各個國家和地區在制定編碼標準的時候,「字符的集合」和「編碼」通常都是同時制定的。所以,日常咱們所說的「字符集」,好比:GB2312, GBK, JIS 等,除了有「字符的集合」這層含義外,同時也包含了「編碼」的含義。學習
注意:Unicode字符集有多種編碼方式,如UTF-八、UTF-16等;ASCII只有一種;大多數MBCS(包括GB2312)也只有一種。編碼
2.1 維基百科的解釋
在計算機科學及相關領域當中,內碼指的是「將資訊編碼後,透過某種方式儲存在特定記憶裝置時,裝置內部的編碼形式」。在不一樣的系統中,會有不一樣的內碼。
在以往的英文系統中,內碼爲ASCII。在繁體中文系統中,目前經常使用的內碼爲大五碼(Big5)。在簡體中文系統中,內碼則爲國標碼(國家標準代碼:如今強制要求使用GB18030標準;較舊計算機仍然使用GB2312)。而統一碼(Unicode)則爲另外一常見內碼。
2.2 百度百科的解釋
內碼是指整機系統中使用的二進制字符編碼,是溝通輸入、輸出與系統平臺之間的交換碼,經過內碼能夠達到通用和高效率傳輸文本的目的。好比MS Word中所存儲和調用的就是內碼而非圖形文字。英文ASCII字符采用一個字節的內碼錶示,中文字符如國標字符集中,GB23十二、GB1234五、GB13000皆用雙字節內碼,GB18030(27,533漢字)雙字節內碼漢字爲20,902個,其他6,631個漢字用四字節內碼。
下面從計算機對多國語言支持的角度來總結字符編碼。
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)。早期的Unicode標準有UCS-二、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.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.1 GB23十二、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-七、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之間,因此確定要用三字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進制是:0110 110001 001001,用這個比特流依次代替模板中的x,獲得:11100110 10110001 10001001,即E6 B1 89。
可見UTF-8是變長的,將Unicode編碼爲00000000-0000007F的字符,用單個字節來表示;00000080-000007FF的字符用兩個字節表示;00000800-0000FFFF的字符用三個字節表示。由於目前爲止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標準之一。想要詳細瞭解的能夠查閱相關資料。
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-八、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.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 字符串」。
8.1 《字符,字節和編碼》http://www.regexlab.com/zh/encoding.htm(強烈推介)
8.4 百度百科《Unicode》http://baike.baidu.com/view/40801.htm
8.5 《Unicode與UTF-8/UTF-16之間有啥聯繫或區別?》http://zhidao.baidu.com/question/52532619.html?fr=ala0