計算機字符與編碼總結

基本概念

1. 字符、字節、字節序與系統內碼
  • 字符:抽象的最小文本單位,在視覺上看到的記號。例如,'2'、'字'、'a'、'$'、'?'
  • 字節:一個8位二進制數,是計算機中存儲數據的單元,有很具體的存儲空間。例如,0x00、0x1A、0xF2
  • 字節序:分爲Big Endian和Little Endian,不同的處理器可能不相同。當一個字符需要多個字節來存儲時,高位字節存儲在低地址的方式屬於Big Endian,反之則屬於Little Endian
  • 系統內碼:指計算機系統中使用的二進制字符編碼,是字符在計算機中的存儲形式。系統內碼包括ASCII、ANSI編碼和UNICODE三大類,其出現時間依次遞進

2. 字符集和編碼
  • 字符集:是指多個字符的集合,屬於人爲制定的一種標準。不同的字符集包含的字符及其數量是不相同的,常見字符集有ASCII、ISO8859、GB2312、BIG5、GB18030、Unicode
  • 編碼:是指以二進制的數字來對應字符集中的字符,是一種從字符到字節的映射,規定了每個“字符”用一個字節還是多個字節存儲,用哪些字節存儲。將字符進行編碼有利於計算機系統對字符進行傳輸、處理和顯示等相關操作,是信息進行交流的基礎。
注意,“字符集”和“編碼”一般都是同時制定的。平常所說的“字符集”,例如ASCII和大多數MBCS(multi-byte character set)等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義,而Unicode字符集則有多種編碼方式,如UTF-8、UTF-16等。

3. 字符編碼發展階段:

從計算機對多國語言的支持角度劃分爲:

  系統內碼 說明 系統
階段一 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,single-byte character set)。例如,"Bob123" 在內存中爲:
42 6F 62 31 32 33 00
           
B o b 1 2 3 \0

在ANSI 編碼階段,每個字符使用一個字節或多個字節來表示,這種方式存放的字符稱爲多字節字符集(MBCS,multi-byte character set。例如,"中文123" 在中文 Windows 95 內存中,每個漢字佔2個字節,每個英文和數字字符佔1個字節:

D6 D0 CE C4 31 32 33 00
           
1 2 3 \0

在 UNICODE 階段,每個字符使用其在 UNICODE 字符集中的序號來表示,目前計算機一般使用 2 個字節存放一個序號,這種方式存放的字符稱作寬字節字符(DBCS,double-byte character set。例如,字符串 "中文123" 在 Windows 2000 下,內存中實際存放的是 5 個:

2D 4E 87 65 31 00 32 00 33 00 00 00      ← 在 x86 CPU 中,低字節在前
             
1 2 3 \0  




常用字符集與編碼

ASCII字符集 (SBCS)
說明:上個世紀60年代,美國製定了一套字符編碼,對英語字符與二進制位之間的關係,做了統一規定。這被稱爲ASCII碼,一直沿用至今。ASCII碼一共規定了128個字符的編碼,比如空格“SPACE”是32(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括32個不能打印出來的控制符號),只佔用了一個字節的後面7位,最前面的1位統一規定爲0。
作用:表語英語及西歐語言。
編碼:ASCII是用7位表示的,能表示128個字符;

ANSI字符集 (MBCS)
說明:後來,由於各國語言的加入,ASCII已經不能滿足信息交流的需要,因此,爲了能夠表示其它國家的文字,各國在ASCII的基礎上制定了自己的字符集,這些從ANSI標準派生的字符集被習慣的統稱爲ANSI字符集,它們正式的名稱應該是MBCS(Multi-Byte Chactacter System,即多字節字符系統)。這些派生字符集的特點是以ASCII 127 bits爲基礎,兼容ASCII 127,他們使用大於128的編碼作爲一個Leading Byte,緊跟在Leading Byte後的第二(甚至第三)個字節與Leading Byte一起作爲實際的編碼。但是,不同的國家有不同的字母,因此,哪怕它們都使用256個符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0—127表示的符號是一樣的,不一樣的只是128—255的這一段。至於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節只能表示256種符號,肯定是不夠的,就必須使用多個字節表達一個符號。比如,簡體中文常見的編碼方式是GB2312,使用兩個字節表示一個漢字,所以理論上最多可以表示256x256=65536個符號。

1. ISO-8859-1字符集 
作用:擴展ASCII,表示西歐、希臘語等。 
編碼:1個字節

2. GB2312字符集  (MBCS)
作用:國家簡體中文字符集,兼容ASCII。 
編碼:使用2個字節表示,能表示7445個符號,包括6763個漢字,幾乎覆蓋所有高頻率漢字。 

3. BIG5字符集  (MBCS)
作用:統一繁體字編碼。 
編碼:使用2個字節表示,表示13053個漢字。 

4. GBK字符集  (MBCS)
作用:它是GB2312的擴展,加入對繁體字的支持,兼容GB2312,但對非拉丁字母語言還是有問題。
編碼:使用2個字節表示,可表示21886個字符。 

5. GB18030字符集  (MBCS)
作用:它解決了中文、日文、朝鮮語等的編碼,兼容GBK。
編碼:它採用變字節表示(1 ASCII,2,4字節),可表示27484個文字。1字節從00到7F; 2字節高字節從81到FE,低字節從40到7E和80到FE;4字節第一三字節從81到FE,第二四字節從30到39。

UCS字符集 (DBCS)
作用:國際標準 ISO 10646 定義了通用字符集 (Universal Character Set)。它是與UNICODE同類的組織,UCS-2和UNICODE兼容。 
編碼:它有UCS-2和UCS-4兩種格式,分別是2字節和4字節。

UNICODE字符集 (DBCS)
說明:世界上存在着多種編碼方式,同一個二進制數字可以被解釋成不同的符號。因此,要想打開一個文本文件,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現亂碼。爲什麼電子郵件常常出現亂碼?就是因爲發信人和收信人使用的編碼方式不一樣。可以想象,如果有一種編碼,將世界上所有的符號都納入其中。每一個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是Unicode,就像它的名字都表示的,這是一種所有符號的編碼。Unicode當然是一個很大的集合,現在的規模可以容納100多萬個符號。每個符號的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字“嚴”。具體的符號對應表,可以查詢unicode.org,或者專門的漢字對應表。 需要注意的是,Unicode只是一個符號集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。比如,漢字“嚴”的unicode是十六進制數4E25,轉換成二進制數足足有15位(100111000100101),也就是說這個符號的表示至少需要2個字節。表示其他更大的符號,可能需要3個字節或者4個字節,甚至更多。這裏就有兩個嚴重的問題,第一個問題是,如何才能區別unicode和ascii?計算機怎麼知道三個字節表示一個符號,而不是分別表示三個符號呢?第二個問題是,我們已經知道,英文字母只用一個字節表示就夠了,如果unicode統一規定,每個符號用三個或四個字節表示,那麼每個英文字母前都必然有二到三個字節是0,這對於存儲來說是極大的浪費,文本文件的大小會因此大出二三倍,這是無法接受的。
它們造成的結果是:1)出現了unicode的多種存儲方式,也就是說有許多種不同的二進制格式,可以用來表示unicode。2)unicode在很長一段時間內無法推廣,直到互聯網的出現。
作用:爲世界650種語言進行統一編碼,兼容ISO-8859-1。
編碼:包括UTF-8,UTF-16和UTF-32三種編碼方式
  • UTF-8:採用變長字節 (1 ASCII, 2 希臘字母, 3 漢字, 4 平面符號) 表示,UTF-8的編碼規則很簡單,只有二條:
    1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。
    2)對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼。
    下表總結了編碼規則,字母x表示可用編碼的位。
    Unicode符號範圍 | UTF-8編碼方式
    (十六進制)                       | (二進制)
    0000 0000-0000 007F | 0xxxxxxx
    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    下面,還是以漢字“嚴”爲例,演示如何實現UTF-8編碼。
    已知“嚴”的unicode是4E25(100111000100101),根據上表,可以發現4E25處在第三行的範圍內(0000 0800-0000 FFFF),因此“嚴”的UTF-8編碼需要三個字節,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然後,從“嚴”的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。這樣就得到了,“嚴”的UTF-8編碼是“11100100 10111000 10100101”,轉換成十六進制就是E4B8A5
  • UTF-16:採用2字節,Unicode中不同部分的字符都同樣基於現有的標準,這是爲了便於轉換。從 0×0000到0×007F是ASCII字符,從0×0080到0×00FF是ISO-8859-1對ASCII的擴展。希臘字母表使用從0×0370到 0×03FF 的代碼,斯拉夫語使用從0×0400到0×04FF的代碼,美國使用從0×0530到0×058F的代碼,希伯來語使用從0×0590到0×05FF的代 碼。中國、日本和韓國的象形文字(總稱爲CJK)佔用了從0×3000到0×9FFF的代碼;由於0×00在c語言及操作系統文件名等中有特殊意義,故很 多情況下需要UTF-8編碼保存文本,去掉這個0×00。
  • UTF-32:採用4字節。
優缺點:
  • UTF-8、UTF-16和UTF-32都可以表示有效編碼空間 (U+000000-U+10FFFF) 內的所有Unicode字符。
  • UTF-8是在互聯網上使用最廣的一種unicode的實現方式。其他實現方式還包括UTF-16和UTF-32,不過在互聯網上基本不用。
  • 使用UTF-8編碼時ASCII字符只佔1個字節,存儲效率比較高,適用於拉丁字符較多的場合以節省空間。
  • 對於大多數非拉丁字符(如中文和日文)來說,UTF-16所需存儲空間最小,每個字符只佔2個字節。
  • Windows NT內核是Unicode(UTF-16),採用UTF-16編碼在調用系統API時無需轉換,處理速度也比較快。
  • 採用UTF-16和UTF-32會有Big Endian和Little Endian之分,而UTF-8則沒有字節順序問題,所以UTF-8適合傳輸和通信。
  • UTF-32採用4字節編碼,一方面處理速度比較快,但另一方面也浪費了大量空間,影響傳輸速度,因而很少使用。


字符編碼判斷

文本文件就是按一定編碼方式將二進制數據表示爲對應的文本,其採用的字符編碼通過可用如下方法判斷:

1. ASCII
  • 每個字節的第1位都是0
2. ANSI編碼
  • GB2312:高字節和低字節的第1位都是1。
  • BIG5、GBK和GB18030:高字節的第1位爲1。操作系統默認的編碼,常爲GBK,可以下載別的並升級。通過高字節的第1位判斷是ASCII或者漢字編碼。
3. UNICODE/UCS
Unicode規範中定義,每一個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫做”零寬度非換行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。這正好是兩個字節,而且FF比FE大1。如果一個文本文件的頭兩個字節是FE FF,就表示該文件採用大頭方式;如果頭兩個字節是FF FE,就表示該文件採用小頭方式。根據前幾個字節可以判斷UNICODE字符集的各種編碼,叫做BOM(Byte Order Mask)方法:
  • UTF-8: EFBBBF (在UCS中沒有含義)
  • UTF-16 Big Endian:FEFF (UCS-2)
  • UTF-16 Little Endian:FFFE (UCS-2)
  • UTF-32 Big Endian:0000FEFF (UCS-4)
  • UTF-32 Little Endian:FFFE0000 (UCS-4)

實例:
打開”記事本“程序Notepad.exe,新建一個文本文件,內容就是一個”嚴“字,依次採用ANSI,Unicode,Unicode big endian 和 UTF-8編碼方式保存。
然後,用文本編輯軟件UltraEdit中的”十六進制功能“,觀察該文件的內部編碼方式。
1)ANSI:文件的編碼就是兩個字節“D1 CF”,這正是“嚴”的GB2312編碼,這也暗示GB2312是採用大頭方式存儲的。
2)Unicode(UCS-2 Little Endian):編碼是四個字節“FF FE 25 4E”,其中“FF FE”表明是小頭方式存儲,真正的編碼是4E25。
3)Unicode big endian(UCS-2 Big Endian):編碼是四個字節“FE FF 4E 25”,其中“FE FF”表明是大頭方式存儲。
4)UTF-8:編碼是六個字節“EF BB BF E4 B8 A5”,前三個字節“EF BB BF”表示這是UTF-8編碼,後三個“E4B8A5”就是“嚴”的具體編碼,它的存儲順序與編碼順序是一致的。


程序代碼中的字符與編碼

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

以上需要注意幾點:
  • Java 中的 char 代表一個“UNICODE 字符(寬字節字符)”,而 C++ 中的 char 代表一個字節。
  • MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函數。

1. Java中相關方法

字符串類 String 中的內容是 UNICODE 字符串:

// Java 代碼,直接寫中文
String
 string = "中文123";

// 得到長度爲 5,因爲是 5 個字符
System.out.println(string.length());

字符串 I/O 操作,字符與字節轉換操作。在 Java 包 java.io.* 中,以“Stream”結尾的類一般是用來操作“字節串”的類,以“Reader”,“Writer”結尾的類一般是用來操作“字符串”的類。

// 字符串與字節串間相互轉化

// 按照 GB2312 得到字節(得到多字節字符串)

byte
 [] bytes = string.getBytes("GB2312");

// 從字節按照 GB2312 得到 UNICODE 字符串
string = new String(bytes, "GB2312");

// 要將 String 按照某種編碼寫入文本文件,有兩種方法:

// 第一種辦法:用 Stream 類寫入已經按照指定編碼轉化好的字節串

OutputStream os = new FileOutputStream("1.txt");
os.write(bytes);
os.close();

// 第二種辦法:構造指定編碼的 Writer 來寫入字符串
Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312");
ow.write(string);
ow.close();

/* 最後得到的 1.txt 和 2.txt 都是 7 個字節 */

如果 java 的源程序編碼與當前默認 ANSI 編碼不符,則在編譯的時候,需要指明一下源程序的編碼。比如:

E:\>javac -encoding BIG5 Hello.java

以上需要注意區分源程序的編碼與 I/O 操作的編碼,前者是在編譯時起作用,後者是在運行時起作用。

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);



參考與閱讀




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章