級別:中級
摘要:本文介紹了字符與編碼的發展過程,相關概念的正確理解。舉例說明了一些實際應用中,編碼的實現方法。然后,本文講述了通常對字符與編碼的幾種誤解,由于這些誤解而導致亂碼產生的原因,以及消除亂碼的辦法。本文的內容涵蓋了“中文問題”,“亂碼問題”。
掌握編碼問題的關鍵是正確地理解相關概念,編碼所涉及的技術其實是很簡單的。因此,閱讀本文時需要慢讀多想,多思考。
引言
“字符與編碼”是一個被經常討論的話題。即使這樣,時常出現的亂碼仍然困擾著大家。雖然我們有很多的辦法可以用來消除亂碼,但我們并不一定理解這些辦法的內在原理。而有的亂碼產生的原因,實際上由于底層代碼本身有問題所導致的。因此,不僅是初學者會對字符編碼感到模糊,有的底層開發人員同樣對字符編碼缺乏準確的理解。
1.?編碼問題的由來,相關概念的理解
1.1?字符與編碼的發展
從計算機對多國語言的支持角度看,大致可以分為三個階段:
|
系統內碼
|
說明
|
系統
|
階段一 |
ASCII |
計算機剛開始只支持英語,其它語言不能夠在計算機上存儲和顯示。 |
英文?DOS |
階段二 |
ANSI編碼 (本地化) |
為使計算機支持更多語言,通常使用?0x80~0xFF?范圍的?2?個字節來表示?1?個字符。比如:漢字?'中'?在中文操作系統中,使用?[0xD6,0xD0]?這兩個字節存儲。
不同的國家和地區制定了不同的標準,由此產生了?GB2312,?BIG5,?JIS?等各自的編碼標準。這些使用?2?個字節來代表一個字符的各種漢字延伸編碼方式,稱為?ANSI?編碼。在簡體中文系統下,ANSI?編碼代表?GB2312?編碼,在日文操作系統下,ANSI?編碼代表?JIS?編碼。
不同?ANSI?編碼之間互不兼容,當信息在國際間交流時,無法將屬于兩種語言的文字,存儲在同一段?ANSI?編碼的文本中。 |
中文?DOS,中文?Windows?95/98,日文?Windows?95/98 |
階段三 |
UNICODE (國際化) |
為了使國際間信息交流更加方便,國際組織制定了?UNICODE?字符集,為各種語言中的每一個字符設定了統一并且唯一的數字編號,以滿足跨語言、跨平臺進行文本轉換、處理的要求。 |
Windows?NT/2000/XP,Linux,Java |
字符串在內存中的存放方法:
在?ASCII?階段,單字節字符串使用一個字節存放一個字符(SBCS)。比如,"Bob123"?在內存中為:
42 |
6F |
62 |
31 |
32 |
33 |
00 |
|
|
|
|
|
|
|
B |
o |
b |
1 |
2 |
3 |
\0 |
在使用?ANSI?編碼支持多種語言階段,每個字符使用一個字節或多個字節來表示(MBCS),因此,這種方式存放的字符也被稱作多字節字符。比如,"中文123"?在中文?Windows?95?內存中為7個字節,每個漢字占2個字節,每個英文和數字字符占1個字節:
D6 |
D0 |
CE |
C4 |
31 |
32 |
33 |
00 |
|
|
|
|
|
|
中 |
文 |
1 |
2 |
3 |
\0 |
在?UNICODE?被采用之后,計算機存放字符串時,改為存放每個字符在?UNICODE?字符集中的序號。目前計算機一般使用?2?個字節(16?位)來存放一個序號(DBCS),因此,這種方式存放的字符也被稱作寬字節字符。比如,字符串?"中文123"?在?Windows?2000?下,內存中實際存放的是?5?個序號:
2D |
4E |
87 |
65 |
31 |
00 |
32 |
00 |
33 |
00 |
00 |
00 |
?????←?在?x86?CPU?中,低字節在前
|
|
|
|
|
|
|
|
中 |
文 |
1 |
2 |
3 |
\0 |
|
一共占?10?個字節。
1.2?字符,字節,字符串
理解編碼的關鍵,是要把字符的概念和字節的概念理解準確。這兩個概念容易混淆,我們在此做一下區分:
|
概念描述
|
舉例
|
字符 |
人們使用的記號,抽象意義上的一個符號。 |
'1',?'中',?'a',?'$',?'¥',?…… |
字節 |
計算機中存儲數據的單元,一個8位的二進制數,是一個很具體的存儲空間。 |
0x01,?0x45,?0xFA,?…… |
ANSI 字符串 |
在內存中,如果“字符”是以?ANSI?編碼形式存在的,一個字符可能使用一個字節或多個字節來表示,那么我們稱這種字符串為?ANSI?字符串或者多字節字符串。 |
"中文123" (占7字節) |
UNICODE 字符串 |
在內存中,如果“字符”是以在?UNICODE?中的序號存在的,那么我們稱這種字符串為?UNICODE?字符串或者寬字節字符串。 |
L"中文123" (占10字節) |
由于不同?ANSI?編碼所規定的標準是不相同的,因此,對于一個給定的多字節字符串,我們必須知道它采用的是哪一種編碼規則,才能夠知道它包含了哪些“字符”。而對于?UNICODE?字符串來說,不管在什么環境下,它所代表的“字符”內容總是不變的。
1.3?字符集與編碼
各個國家和地區所制定的不同?ANSI?編碼標準中,都只規定了各自語言所需的“字符”。比如:漢字標準(GB2312)中沒有規定韓國語字符怎樣存儲。這些?ANSI?編碼標準所規定的內容包含兩層含義:
- 使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含“字符”的集合就叫做“字符集”。??
- 規定每個“字符”分別用一個字節還是多個字節存儲,用哪些字節來存儲,這個規定就叫做“編碼”。??
各個國家和地區在制定編碼標準的時候,“字符的集合”和“編碼”一般都是同時制定的。因此,平常我們所說的“字符集”,比如:GB2312,?GBK,?JIS?等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。
“UNICODE?字符集”包含了各種語言中使用到的所有“字符”。用來給?UNICODE?字符集編碼的標準有很多種,比如:UTF-8,?UTF-7,?UTF-16,?UnicodeLittle,?UnicodeBig?等。
1.4?常用的編碼簡介
簡單介紹一下常用的編碼規則,為后邊的章節做一個準備。在這里,我們根據編碼規則的特點,把所有的編碼分成三類:
分類
|
編碼標準
|
說明
|
單字節字符編碼 |
ISO-8859-1 |
最簡單的編碼規則,每一個字節直接作為一個?UNICODE?字符。比如,[0xD6,?0xD0]?這兩個字節,通過?iso-8859-1?轉化為字符串時,將直接得到?[0x00D6,?0x00D0]?兩個?UNICODE?字符,即?"?D"。
反之,將?UNICODE?字符串通過?iso-8859-1?轉化為字節串時,只能正常轉化?0~255?范圍的字符。 |
ANSI?編碼 |
GB2312, BIG5, Shift_JIS, ISO-8859-2?…… |
把?UNICODE?字符串通過?ANSI?編碼轉化為“字節串”時,根據各自編碼的規定,一個?UNICODE?字符可能轉化成一個字節或多個字節。
反之,將字節串轉化成字符串時,也可能多個字節轉化成一個字符。比如,[0xD6,?0xD0]?這兩個字節,通過?GB2312?轉化為字符串時,將得到?[0x4E2D]?一個字符,即?'中'?字。
“ANSI?編碼”的特點: 1.?這些“ANSI?編碼標準”都只能處理各自語言范圍之內的?UNICODE?字符。 2.?“UNICODE?字符”與“轉換出來的字節”之間的關系是人為規定的。 |
UNICODE?編碼 |
UTF-8, UTF-16,?UnicodeBig?…… |
與“ANSI?編碼”類似的,把字符串通過?UNICODE?編碼轉化成“字節串”時,一個?UNICODE?字符可能轉化成一個字節或多個字節。
與“ANSI?編碼”不同的是: 1.?這些“UNICODE?編碼”能夠處理所有的?UNICODE?字符。 2.?“UNICODE?字符”與“轉換出來的字節”之間是可以通過計算得到的。 |
我們實際上沒有必要去深究每一種編碼具體把某一個字符編碼成了哪幾個字節,我們只需要知道“編碼”的概念就是把“字符”轉化成“字節”就可以了。對于“UNICODE?編碼”,由于它們是可以通過計算得到的,因此,在特殊的場合,我們可以去了解某一種“UNICODE?編碼”是怎樣的規則。