從計算機對多國語言的支持角度看,大體能夠分爲三個階段:程序員
系統內碼 | 說明 | 系統 | |
階段一 | 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" 在內存中爲:ide
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 個字節。編碼
2: 字符,字節,字符串spa
字節:8位byte爲一字節,它是計算機中存儲數據的單元,佔8位二進制數。操作系統
字符:根據編碼方式的不一樣,字符所佔的字節個數也不一樣,如上例所述。code
ASSCII:一個字符佔用一個字節內存
ANSI:一個字符能夠佔一個或多個字節,好比,a佔一個字節,而中文字符通常佔二個字節
UNICODE:一個字符佔二個字節,對於英文字符也是,可經過低字節填充來實現
字符串:它由字符組成
3:字符集和編碼
使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含「字符」的集合就叫作「字符集」。好比:GB2312, GBK, JIS 等
規定每一個「字符」分別用一個字節仍是多個字節存儲,用哪些字節來存儲,這個規定就叫作「編碼」。
各國因語言不一樣,包含的字符也不一樣, 那麼在編碼的時候各自的字符集也不盡相同,例如:漢字標準(GB2312)
注:「UNICODE 字符集」包含了各類語言中使用到的全部「字符」。用來給 UNICODE 字符集編碼的標準有不少種,好比:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig、
4:經常使用的編碼
簡單介紹一下經常使用的編碼規則,爲後邊的章節作一個準備。在這裏,咱們根據編碼規則的特色,把全部的編碼分紅三類:
分類 | 編碼標準 | 說明 |
單字節字符編碼 | ISO-8859-1 | 最簡單的編碼規則,每個字節直接做爲一個 UNICODE 字符。好比,[0xD6, 0xD0] 這兩個字節,經過 iso-8859-1 轉化爲字符串時,將直接獲得 [0x00D6, 0x00D0] 兩個 UNICODE 字符,即 "ÖÐ"。 反之,將 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 字符」與「轉換出來的字節」之間是能夠經過計算獲得的。 |
5:字符與編碼在代碼中的實現
在 C++ 和 Java 中,用來表明「字符」和「字節」的數據類型,以及進行編碼的方法:
類型或操做 | C++ | Java |
字符 | wchar_t | char |
字節 | char | byte |
ANSI 字符串 | char[] | byte[] |
UNICODE 字符串 | wchar_t[] | String |
字節串→字符串 | mbstowcs(), MultiByteToWideChar() | string = new String(bytes, "encoding") |
字符串→字節串 | wcstombs(), WideCharToMultiByte() | bytes = string.getBytes("encoding") |
以上須要注意幾點:
聲明一段字符串常量:
// ANSI 字符串,內容長度 7 字節 char sz[20] = "中文123"; // UNICODE 字符串,內容長度 5 個 wchar_t(10 字節) wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033"; |
UNICODE 字符串的 I/O 操做,字符與字節的轉換操做:
// 運行時設定當前 ANSI 編碼,VC 格式 setlocale(LC_ALL, ".936"); // GCC 中格式 setlocale(LC_ALL, "zh_CN.GBK"); // Visual C++ 中使用小寫 %s,按照 setlocale 指定編碼輸出到文件 // GCC 中使用大寫 %S fwprintf(fp, L"%s\n", wsz); // 把 UNICODE 字符串按照 setlocale 指定的編碼轉換成字節 wcstombs(sz, wsz, 20); // 把字節串按照 setlocale 指定的編碼轉換成 UNICODE 字符串 mbstowcs(wsz, sz, 20); |
在 Visual C++ 中,UNICODE 字符串常量有更簡單的表示方法。若是源程序的編碼與當前默認 ANSI 編碼不符,則須要使用 #pragma setlocale,告訴編譯器源程序使用的編碼:
// 若是源程序的編碼與當前默認 ANSI 編碼不一致, // 則須要此行,編譯時用來指明當前源程序使用的編碼 #pragma setlocale(".936") // UNICODE 字符串常量,內容長度 10 字節 wchar_t wsz[20] = L"中文123"; |
以上須要注意 #pragma setlocale 與 setlocale(LC_ALL, "") 的做用是不一樣的,#pragma setlocale 在編譯時起做用,setlocale() 在運行時起做用。
對編碼的誤解 | |
誤解一 | 在將「字節串」轉化成「UNICODE 字符串」時,好比在讀取文本文件時,或者經過網絡傳輸文本時,容易將「字節串」簡單地做爲單字節字符串,採用每「一個字節」就是「一個字符」的方法進行轉化。 而實際上,在非英文的環境中,應該將「字節串」做爲 ANSI 字符串,採用適當的編碼來獲得 UNICODE 字符串,有可能「多個字節」才能獲得「一個字符」。 一般,一直在英文環境下作開發的程序員們,容易有這種誤解。 |
誤解二 | 在 DOS,Windows 98 等非 UNICODE 環境下,字符串都是以 ANSI 編碼的字節形式存在的。這種以字節形式存在的字符串,必須知道是哪一種編碼才能被正確地使用。這使咱們造成了一個慣性思惟:「字符串的編碼」。 當 UNICODE 被支持後,Java 中的 String 是以字符的「序號」來存儲的,不是以「某種編碼的字節」來存儲的,所以已經不存在「字符串的編碼」這個概念了。只有在「字符串」與「字節串」轉化時,或 者,將一個「字節串」當成一個 ANSI 字符串時,纔有編碼的概念。 很多的人都有這個誤解。 |
第一種誤解,每每是致使亂碼產生的緣由。第二種誤解,每每致使原本容易糾正的亂碼問題變得更復雜。
在這裏,咱們能夠看到,其中所講的「誤解一」,即採用每「一個字節」就是「一個字符」的轉化方法,實際上也就等同於採用 iso-8859-1 進行轉化。所以,咱們經常使用 bytes = string.getBytes("iso-8859-1") 來進行逆向操做,獲得原始的「字節串」。而後再使用正確的 ANSI 編碼,好比 string = new String(bytes, "GB2312"),來獲得正確的「UNICODE 字符串」。
非 UNICODE 程序中的字符串,都是以某種 ANSI 編碼形式存在的。若是程序運行時的語言環境與開發時的語言環境不一樣,將會致使 ANSI 字符串的顯示失敗。
好比,在日文環境下開發的非 UNICODE 的日文程序界面,拿到中文環境下運行時,界面上將顯示亂碼。若是這個日文程序界面改成採用 UNICODE 來記錄字符串,那麼當在中文環境下運行時,界面上將能夠顯示正常的日文。
因爲客觀緣由,有時候咱們必須在中文操做系統下運行非 UNICODE 的日文軟件,這時咱們能夠採用一些工具,好比,南極星,AppLocale 等,暫時的模擬不一樣的語言環境。