1. 程式人生 > >字符集與字元編碼簡介(轉)

字符集與字元編碼簡介(轉)

我們知道,計算機只能識別諸如0101這樣的二進位制數,於是人們必須以二進位制資料與計算機進行互動,或者先將人類使用的字元按一定規則轉換為二進位制數。

那什麼是字元呢?在計算機領域,我們把諸如文字、標點符號、圖形符號、數字等統稱為字元。而由字元組成的集合則成為字符集,字符集由於包含字元的多少與異同而形成了各種不同的字符集。我們知道,所有字元在計算機中都是以二進位制來儲存的。那麼一個字元究竟由多少個二進位制位來表示呢?這就涉及到字元編碼的概念了,比如一個字符集有8個字元,那麼用3個二進位制位就可以完全表示該字符集的所有字元,也即每個字元用3個二進位制位進行編碼。

我們規定字元編碼必須完成如下兩件事:

(1) 規定一個字符集中的字元由多少個位元組表示;

(2) 制定該字符集的字元編碼表,即該字符集中每個字元對應的(二進位制)值。

1. ASCII 碼:

上個世紀60年代,美國製定了一套字元編碼標準,對英語字元與二進位制位之間的關係,做了統一規定。這被稱為ASCII碼,一直沿用至今。

ASCII(American Standard Code for Information Interchange),是一種字元編碼標準,它的字符集為英文字符集,它規定字符集中的每個字元均由一個位元組表示,指定了字元表編碼表,稱為ASCII碼錶。它已被國際標準化組織定義為國際標準,稱為ISO646標準。

ASCII碼一共規定了128個字元的編碼,比如空格“SPACE”是32(二進位制00100000),大寫的字母A是65(二進位制01000001)等。這128個符號(包括32個不能打印出來的控制符號),只佔用了一個位元組的後面7位,最前面的1位統一規定為0。這種採用一個位元組來編碼128個字元的ASCII碼稱為標準 ASCII 碼或者基礎ASCII碼。

在標準ASCII碼錶中,0~31及127(共33個)是控制字元或通訊專用字元,如控制符:LF(換行)、CR(回車)、FF(換頁)、DEL(刪除)、BS(退格)、BEL(振鈴)等;通訊專用字元:SOH(文頭)、EOT(文尾)、ACK(確認)等;ASCII值為 8、9、10 和 13 分別轉換為退格、製表、換行和回車字元。它們並沒有特定的圖形顯示,但會依不同的應用程式,而對文字顯示有不同的影響。
32~126(共95個)是可顯示字元,其中32是空格,48~57為0到9十個阿拉伯數字;65~90為26個大寫英文字母,97~122號為26個小寫英文字母,其餘為一些標點符號、運算子號等。
同時還要注意,在標準ASCII中,其最高位(b7)可用作奇偶校驗位。所謂奇偶校驗,是指在程式碼傳送過程中用來檢驗是否出現錯誤的一種方法,一般分奇校驗和偶校驗兩種。奇校驗規定:正確的程式碼一個位元組中1的個數必須是奇數,若非奇數,則在最高位b7添1;偶校驗規定:正確的程式碼一個位元組中1的個數必須是偶數,若非偶數,則在最高位b7添1。

但是,由於標準 ASCII 字符集字元數目有限,在實際應用中往往無法滿足要求。為此,國際標準化組織又制定了 ISO 2022 標準,它規定了在保持與 ISO646 相容的前提下將 ASCII 字符集擴充為 8 位程式碼的統一方法。 ISO 陸續制定了一批適用於不同地區的擴充 ASCII 字符集,每種擴充 ASCII 字符集分別可以擴充 128 個字元,這些擴充字元的編碼均為高位為 1 的 8 位程式碼(即十進位制數 128~255 ),稱為擴充套件 ASCII 碼。

但是需要注意,各種擴充套件ASCII碼除了編碼為0~127的字元外,編碼為128~255的字元並不相同。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג),在俄語編碼中又會代表另一個符號。

2. ANSI編碼標準

標準ASCII碼和擴充套件ASCII碼滿足了西語國家的需求,但是,隨著計算機在世界範圍內的普及,對於亞洲國家,如中日韓等國來說,他們使用的符號很多,ASCII字元編碼標準遠遠不能滿足其需要,於是這些國家便針對本國的字符集指定了相應的字元編碼標準,如GB2312、BIG5、JIS等僅適用於本國字符集的編碼標準。這些字元編碼標準統稱為ANSI編碼標準,這些ANSI編碼標準有一些共同的特點:

(1) 每種ANSI字符集只規定自己國家或地區使用的語言所需的'字元';比如簡體中文編碼標準GB-2312的字符集中就不會包含韓國人的文字。

(2) ANSI字符集的空間都比ASCII要大很多,一個位元組已經不夠,絕大多數ANSI編碼標準都使用多個位元組來表示一個字元。

(3) ANSI編碼標準一般都會相容ASCII碼。

這裡要特別提一下我國的幾種字元編碼標準:GB2312、GBK、GB18030。

字元必須編碼後才能被計算機處理。計算機使用的預設編碼方式就是計算機的內碼。早期的計算機使用7位的ASCII編碼(標準ASCII編碼),為了處理漢字,程式設計師設計了用於簡體中文的GB2312和用於繁體中文的big5。
GB2312(1980年)一共收錄了7445個字元,包括6763個漢字和682個其它符號。漢字區的內碼範圍高位元組從B0-F7,低位元組從A1-FE,佔用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE。 GB2312支援的漢字太少。1995年的漢字擴充套件規範GBK1.0收錄了21886個符號,它分為漢字區和圖形符號區。漢字區包括21003個字元。2000年的GB18030是取代GBK1.0的正式國家標準。該標準收錄了27484個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數民族文字。現在的PC平臺必須支援GB18030,對嵌入式產品暫不作要求。所以手機、MP3一般只支援GB2312。
從ASCII、GB2312、GBK到GB18030,這些編碼標準是向下相容的,即同一個字元在這些標準中總是有相同的編碼,後面的標準支援更多的字元。在這些編碼中,英文和中文可以統一地處理。區分中文編碼的方法是高位元組的最高位不為0。按照程式設計師的稱呼,GB2312、GBK到GB18030都屬於雙位元組字符集 (DBCS)。 GB 18030是中國所有非手持/嵌入式計算機系統的強制實施標準。

例如,在Windows中開啟記事本,"另存為"對話方塊的"編碼"下拉框中有一項ANSI編碼,ANSI是預設的編碼方式。對於英文檔案是ASCII編碼,對於簡體中文檔案是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會採用Big5碼),在日文作業系統下,ANSI 編碼代表 JIS 編碼,其他語言的系統的情況類似。

3. UnicodeUCSUTF

但是隨著網際網路的興起,問題又出現了。由於ANSI碼的第一個特點:各個國家或地區在編制自己的ANSI碼時並未考慮到其他國家或地區的ANSI碼,導致編碼空間有重疊,比如:漢字'中'的GB編碼是[0xD6,0xD0],這個編碼在其他國家的ANSI編碼標準中則不一定就是該編碼了。於是,同一個二進位制數字可以被解釋成不同的符號。因此,要想開啟一個文字檔案,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現亂碼。這樣一來當在不同ANSI編碼標準之間進行資訊交換和顯示的時候,亂碼就不可避免了。

(1)Unicode

可以想象,如果有一種編碼,將世界上所有的符號都納入其中,每一個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是Unicode,就像它的名字所表示的,這是一種所有符號的編碼。

Unicode是Universal Multiple-Octet Coded Character Set的縮寫,中文含義是"通用多八位編碼字符集"。它是由一個名為 Unicode學術學會(Unicode.org)的機構制訂的字元編碼標準,Unicode目標是將世界上絕大多數國家的文字、符號都編入其字符集,它為每種語言中的每個字元設定了統一併且唯一的二進位制編碼,以滿足跨語言、跨平臺進行文字轉換、處理的要求,以達到支援現今世界各種不同語言的書面文字的交換、處理及顯示的目的,使世界範圍人們通過計算機進行資訊交換時達到暢通自如而無障礙。由於一個Unicode字元用多個位元組表示。這樣Unicode編碼在不同平臺儲存時就要注意其位元組序了。比如:採用標準Unicode編碼的'中'在X86平臺上(big endian)的儲存就是'2D4E',而在SPARC Solaris上(little endian)的儲存則是'4E2D'。

(2) UCS

那什麼又是UCS呢,它與Unicode有何關係?

歷史上, 有兩個獨立創立統一字符集的嘗試。一個是國際標準化組織(ISO)的 ISO 10646 專案, 另一個是由(一開始大多是美國的)多語言軟體製造商組成的協會(unicode.org)組織的 Unicode 專案. 幸運的是, 1991年前後, 兩個專案的參與者都認識到, 世界不需要兩個不同的統一字符集. 它們合併雙方的工作成果, 併為創立一個統一編碼表而協同工作。現在,兩個專案仍都存在並獨立地公佈各自的標準, 但 Unicode 協會和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 標準的碼錶相容, 並緊密地共同調整任何未來的擴充套件。

國際標準 ISO 10646 定義了通用字符集 (Universal Character Set) UCS。 UCS 是所有其他字符集標準的一個超集. 它保證與其他字符集是雙向相容的. 就是說, 如果你將任何文字字串翻譯到 UCS格式, 然後再翻譯回原編碼, 你不會丟失任何資訊。

ISO 10646 定義了一個 31 位的字符集。 然而, 在這巨大的編碼空間中, 迄今為止只分配了前 65534 個碼位 (0x0000 到 0xFFFD). 這個 UCS 的 16位子集稱為基本多語言面(Basic Multilingual Plane, BMP)。 將被編碼在 16 位 BMP 以外的字元都屬於非常特殊的字元(比如象形文字), 且只有專家在歷史和科學領域裡才會用到它們。按當前的計劃, 將來也許再也不會有字元被分配到從 0x000000 到 0x10FFFF 這個覆蓋了超過 100 萬個潛在的未來字元的 21 位的編碼空間以外去了。ISO 10646-1 標準第一次發表於 1993 年, 定義了字符集與 BMP 中內容的架構。定義 BMP 以外的字元編碼的第二部分 ISO 10646-2 正在準備中, 但也許要過好幾年才能完成. 新的字元仍源源不斷地加入到 BMP 中, 但已經存在的字元是穩定的且不會再改變了。

UCS 不僅給每個字元分配一個程式碼, 而且賦予了一個正式的名字. 表示一個 UCS值的十六進位制數, 通常在前面加上 "U+", 就象 U+0041 代表字元"拉丁大寫字母A"。UCS 字元 U+0000 到 U+007F 與 US-ASCII(ISO 646) 是一致的, U+0000 到 U+00FF 與 ISO 8859-1(Latin-1) 也是一致的。從 U+E000 到 U+F8FF, 已經 BMP 以外的大範圍的編碼是為私用保留的。

Unicode 字元編碼標準與 ISO 10646 的通用字符集(Universal Character Set,UCS)概念相對應,目前的用於實用的 Unicode 版本對應於 UCS-2,即使用16位來表示一個Unicode字元。也就是每個字元佔用2個位元組。這樣理論上一共最多可以表示 65,536(2的16次方) 個字元。基本滿足各種語言的使用。實際上目前版本的 Unicode 尚未填充滿這16位編碼,保留了大量空間作為特殊使用或將來擴充套件。未來版本會擴充到 ISO 10646-1 實現級別3,即涵蓋 UCS-4 的所有字元。UCS-4 是一個更大的尚未填充完全的31位字符集,加上恆為0的首位,共需佔據32位,即4位元組。理論上最多能表示 2,147,483,648(2的31次方)個字元,完全可以涵蓋一切語言所用的符號。

由於Unicode 編碼標準與UCS編碼標準是相互相容的,為了方便敘述,下面把二者作為一個統一編碼標準來敘述。

(3)UTF

Unicode(UCS)只是一個字符集,它只規定了符號的二進位制程式碼,卻沒有規定這個二進位制程式碼應該如何儲存。

比如,漢字“嚴”的unicode(UCS)碼是十六進位制數4E25,轉換成二進位制數足足有15位(100111000100101),也就是說這個符號的表示至少需要2個位元組。表示其他更大的符號,可能需要3個位元組或者4個位元組,甚至更多。

這裡就有兩個嚴重的問題,第一個問題是,如何才能區別unicode和ascii?計算機怎麼知道三個位元組表示一個符號,而不是分別表示三個符號呢?第二個問題是,我們已經知道,英文字母只用一個位元組表示就夠了,如果unicode統一規定,每個符號用三個或四個位元組表示,那麼每個英文字母前都必然有二到三個位元組是0,這對於儲存來說是極大的浪費,文字檔案的大小會因此大出二三倍,這是無法接受的。

為了解決這些問題,就出現了UTF。

UTF(Unicode Translation Format),它是Unicode (UCS)的實現(或儲存)方式,稱為Unicode轉換格式。Unicode 的實現方式不同於編碼方式。一個字元的 Unicode 編碼是確定的。但是在實際傳輸過程中,由於不同系統平臺的設計不一定一致,以及出於節省空間的目的,對 Unicode 編碼的實現方式有所不同。

UTF有三種實現方式:

UTF-16:其本身就是標準的Unicode編碼方案,又稱為UCS-2,它固定使用16 bits(兩個位元組)來表示一個字元。
UTF-32:又稱為UCS-4,它固定使用32 bits(四個位元組)來表示一個字元。
UTF-8:最廣泛的使用的UTF方案,UTF-8使用可變長度位元組來儲存Unicode字元,例如ASCII字母繼續使用1位元組儲存,重音文字、希臘字母或西裡爾字母等使用2位元組來儲存,而常用的漢字就要使用3位元組。輔助平面字元則使用4位元組。UTF-8更便於在使用Unicode的系統與現存的單位元組的系統進行資料傳輸和交換。與前兩個方案不同:UTF-8以位元組為編碼單元,沒有位元組序的問題。

UTF有三種方案,那麼如何在接收資料和儲存資料時識別資料採用的是哪個方案呢?

Unicode(UCS)規範中推薦的標記位元組順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte order Mark。

在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編碼了。Windows就是使用BOM來標記文字檔案的編碼方式的。

這樣根據識別前面的"ZERO WIDTH NO-BREAK SPACE"字元即可識別編碼方案,位元組流中前幾個位元組所表示的編碼方式如下:
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian
FF FE UTF-16/UCS-2, big endian
FE FF 00 00 UTF-32/UCS-4, little endian.
00 00 FF FE UTF-32/UCS-4, big-endian.

在微軟公司Windows XP作業系統附帶的記事本中,“另存為”對話方塊可以選擇的四種編碼方式除去非 Unicode 編碼的 ANSI 外,其餘三種“Unicode” 、“Unicode big endian” 和 “UTF-8” 分別對應UTF-16小端(BOM)、UTF-16大端(BOM)和 UTF-8這三種實現方式。

另外,內碼是指作業系統內部的字元編碼。早期作業系統的內碼是與語言相關的。目前Windows的核心已經支援Unicode字符集,這樣在核心上可以支援全世界所有的語言文字。但是由於現有的大量程式和文件都採用了某種特定語言的編碼,例如GBK,Windows不可能不支援現有的編碼,而全部改用Unicode。於是Windows就使用內碼表(code page)來適應各個國家和地區不同的字符集。

而所謂內碼表(code page)就是針對一種語言文字的字元編碼。例如GBK的code page是CP936,BIG5的code page是CP950,GB2312的code page是CP20936。

微軟一般將預設內碼表指定的編碼說成是內碼。預設內碼表指的是:預設用什麼編碼來解釋字元。例如Windows的記事本打開了一個文字檔案,裡面的內容是位元組流:BA、BA、D7、D6。Windows應該去怎麼解釋它呢?

是按照Unicode編碼解釋、還是按照GBK解釋、還是按照BIG5解釋,還是按照ISO8859-1去解釋?如果按GBK去解釋,就會得到“漢字”兩個字。按照其它編碼解釋,可能找不到對應的字元,也可能找到錯誤的字元。所謂“錯誤”是指與文字作者的本意不符,這時就產生了亂碼。

答案是Windows按照當前的預設內碼表去解釋文字檔案裡的位元組流。預設內碼表可以通過控制面板的區域選項設定。記事本的另存為中有一項ANSI,其實該項就是指按照預設內碼表的編碼方法儲存。

1:Unicode編碼轉換為UTF-8編碼的方法

UTF-8就是以8位為單元對Unicode進行編碼。下面是Unicode和UTF-8轉換的規則:

Unicode UTF-8

0000 - 007F 0xxxxxxx

0080 - 07FF 110xxxxx 10xxxxxx

0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx

例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間,所以肯定要用3位元組模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進位制是:0110 110001 001001, 用這個位元流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

2

當一個軟體開啟一個文字時,它要做的第一件事是決定這個文字究竟是使用哪種字符集的哪種編碼儲存的。軟體一般採用三種方式來決定文字的字符集和編碼:檢測檔案頭標識,提示使用者選擇,根據一定的規則猜測。

最標準的途徑是檢測文字最開頭的幾個位元組。

例如,當你在 windows 的記事本里新建一個檔案,輸入"聯通"兩個字之後,儲存,關閉,然後再次開啟,你會發現這兩個字已經消失了,代之的是幾個亂碼。當你新建一個文字檔案時,記事本的編碼預設是ANSI(代表系統預設編碼,在中文系統中是GB系列編碼)。在這種編碼下,"聯通"的內碼是:

c1 1100 0001

aa 1010 1010

cd 1100 1101

a8 1010 1000

注意,第一二個位元組、第三四個位元組的起始部分的都是"110"和"10",正好與UTF8規則裡的兩位元組模板是一致的。於是當我們再次開啟記事本時,記事本就誤認為 這是一個UTF8編碼的檔案,讓我們把第一個位元組的110和第二個位元組的10去掉,我們就得到了"00001 101010",再把各位對齊,補上前導的0,就得到了"0000 0000 0110 1010",不好意思,這是UNICODE的006A,也就是小寫的字母"j",而之後的兩位元組用UTF8解碼之後是0368,這個字元什麼也不是。這就 是隻有"聯通"兩個字的檔案沒有辦法在記事本里正常顯示的原因。

其實,如果記事本軟體通過檢測檔案頭標識來確定檔案的編碼方式就可避免該情況,即如果是UTF-8檔案,則其檔案前三個位元組應該是EF BB BF。

3

C runtime library中的字串操作函式(str字首)是用來處理ASCII的。

Windows提供了處理ANSI字串的函式,以mbs為字首,定義在mbstring.h中。

Windows提供了處理Unicode字串的函式,以wcs為字首,定義在string.h和wchar.h中。