字符、字節及其編碼

從計算機對多國語言的支持角度看,大體能夠分爲三個階段:程序員

系統內碼 說明 系統
階段一 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:字符與編碼在代碼中的實現

5.1 程序中的字符與字節

在 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")

以上須要注意幾點:

  1. Java 中的 char 表明一個「UNICODE 字符(寬字節字符)」,而 C++ 中的 char 表明一個字節。
  2. MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函數。
5.2 C++ 中相關實現方法

聲明一段字符串常量:

// 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 等,暫時的模擬不一樣的語言環境。

相關文章
相關標籤/搜索