字符集與編碼

字符集與編碼

 

 

縮寫含義函數

ASCII: American Standard Code for Information Interchange測試

UCS: Universal Character Setthis

UTF: Unicode/UCS Transformation Format編碼

 

ASCII編碼

ASCII碼是7位編碼,編碼範圍是0x00-0x7F。ASCII字符集包括英文字母、阿拉伯數字和標點符號等字符。其中0x00-0x20和0x7F共33個控制字符。spa

只支持ASCII碼的系統會忽略每一個字節的最高位,只認爲低7位是有效位。設計

英語用128個符號編碼就夠了,可是用來表示其餘語言,128個符號是不夠的。好比,在法語中,字母上方有注音符號,它就沒法用ASCII碼錶示。因而,一些歐洲國家就決定,利用字節中閒置的最高位編入新的符號。好比,法語中的é的編碼爲130(二進制10000010)。這樣一來,這些歐洲國家使用的編碼體系,能夠表示最多256個符號。code

可是,這裏又出現了新的問題。不一樣的國家有不一樣的字母,所以,哪怕它們都使用256個符號的編碼方式,表明的字母卻不同。好比,130在法語編碼中表明瞭é,在希伯來語編碼中卻表明了字母Gimel (ג),在俄語編碼中又會表明另外一個符號。可是無論怎樣,全部這些編碼方式中,0--127表示的符號是同樣的,不同的只是128--255的這一段。orm

至於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節只能表示256種符號,確定是不夠的,就必須使用多個字節表達一個符號。好比,簡體中文常見的編碼方式是GB2312,使用兩個字節表示一個漢字,因此理論上最多能夠表示256x256=65536個符號。htm

 

Unicode字符集

正如上一節所說,世界上存在着多種編碼方式,同一個二進制數字能夠被解釋成不一樣的符號。所以,要想打開一個文本文件,就必須知道它的編碼方式,不然用錯誤的編碼方式解讀,就會出現亂碼。爲何電子郵件經常出現亂碼?就是由於發信人和收信人使用的編碼方式不同。blog

能夠想象,若是有一種編碼,將世界上全部的符號都歸入其中。每個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是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在很長一段時間內沒法推廣,直到互聯網的出現。

 

UTF-8編碼

互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8就是在互聯網上使用最廣的一種Unicode的實現方式。其餘實現方式還包括UTF-16(字符用兩個字節或四個字節表示)和UTF-32(字符用四個字節表示),不過在互聯網上基本不用。重複一遍,這裏的關係是,UTF-8是Unicode的實現方式之一。

UTF-8最大的一個特色,就是它是一種變長的編碼方式。它可使用1~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編碼很是簡單。若是一個字節的第一位是0,則這個字節單獨就是一個字符;若是第一位是1,則連續有多少個1,就表示當前字符佔用多少個字節。

下面,仍是以漢字"嚴"爲例,演示如何實現UTF-8編碼。

已知"嚴"的unicode是4E25(100111000100101),根據上表,能夠發現4E25處在第三行的範圍內(0000 0800-0000 FFFF),所以"嚴"的UTF-8編碼須要三個字節,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。而後,從"嚴"的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。這樣就獲得了,"嚴"的UTF-8編碼是"11100100 10111000 10100101",轉換成十六進制就是E4B8A5。

 

Little endian和Big endian

這兩個古怪的名稱來自英國做家斯威夫特的《格列佛遊記》。在該書中,小人國裏爆發了內戰,戰爭原由是人們爭論,吃雞蛋時到底是從大頭(Big-Endian)敲開仍是從小頭(Little-Endian)敲開。爲了這件事情,先後爆發了六次戰爭,一個皇帝送了命,另外一個皇帝丟了王位。

所以,第一個字節在前,就是"大頭方式"(Big endian),第二個字節在前就是"小頭方式"(Little endian)。

那麼很天然的,就會出現一個問題:計算機怎麼知道某一個文件到底採用哪種方式編碼?

Unicode規範中定義,每個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫作"零寬度非換行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。這正好是兩個字節,並且FF比FE大1。

若是一個文本文件的頭兩個字節是FE FF,就表示該文件採用大頭方式;若是頭兩個字節是FF FE,就表示該文件採用小頭方式。

 

 

UTF-16編碼

16bit編碼,一個字符佔2byte;
Unicode的preferred encoding,不兼容ASCII;
與CPU字節序有關,可分爲UTF-16LE和UTF-16BE;


UTF-32編碼

32bit編碼,一個字符佔4byte;

 

小結

對於純英文文檔,或者混合文檔中英文字符佔了大多數,UTF-8編碼更有優點(節省存儲空間);
對於純中文文檔,或者混合文檔中中文字符佔了大多數,UTF-16編碼更有優點;
UTF-8須要判斷每一個字節中的開頭標誌信息,因此若是一當某個字節在傳送過程當中出錯了,就會致使後面的字節也會解析出錯;
而UTF-16不會判斷開頭標誌,即便錯也只會錯一個字符,因此容錯能力更強。

例如:中文字符 「漢」 對應的unicode是6C49,
UTF-16表示:01101100 01001001
UTF-8表示:十進制值爲27721,須要使用3個byte

 

文本開頭的標誌可用於判斷對應的編碼:
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian
FF FE UTF-16/UCS-2, big endian
FF FE 00 00 UTF-32/UCS-4, little endian.
00 00 FE FF UTF-32/UCS-4, big-endian.

注意:utf-16和utf-32,若是用char字符串的話,會有0字節出如今非真正結束的地方(用wchar_t能夠避免這個問題)。

 

utf-八、utf-16和utf-32是unicode字符集的不一樣編碼方式;

對於中文而言,還有另一套字符集——區位碼,區位碼字符集與unicode沒有任何關係,區位碼的編碼方式主要有GB2312和GBK,下面將繼續介紹。

 


 

區位碼字符集

區位碼把編碼表分爲94個區,每一個區對應94個位,每一個字符的區號和位號組合起來就是該漢字的區位碼。區位碼通常 用10進制數來表示,如1601就表示16區1位,對應的字符是「啊」。

區位碼中01-09區是符號、數字區,16-87區是漢字區,10-15和88-94是未定義的空白區。它將收錄的漢字分紅兩級:第一級是經常使用漢字計3755個,置於16-55區,按漢語拼音字母/筆形順序排列;第二級漢字是次經常使用漢字計3008個,置於56-87區,按部首/筆畫順序排列。一級漢字是按照拼音排序的,這個就能夠獲得某個拼音在一級漢字區位中的範圍,不少根據漢字能夠獲得拼音的程序就是根據這個原理編寫的。

區位碼更應該認爲是字符集的定義,定義了所收錄的字符和字符位置,而GB2312和GBK是計算機環境中支持這種字符集的編碼。區位碼和GB2312編碼的關係有點像 Unicode字符集和UTF-8編碼。

 

GBK2312編碼

GBK2312是簡體中文的字符集編碼,它基於區位碼設計的。在區位碼的區號和位號上分別加上0xA0就獲得了GB2312編碼。

GB2312字符集中除經常使用簡體漢字字符外還包括希臘字母、日文平假名及片假名字母、俄語西裏爾字母等字符,未收錄繁體中文漢字和一些生僻字。能夠用繁體漢字測試某些系統是否是隻支持GB2312編碼。

GB2312是雙字節編碼,編碼範圍是0xA1A1-0x7E7E,去掉未定義的區域以後能夠理解爲實際編碼範圍是0xA1A1-0xF7FE。
EUC-CN能夠理解爲GB2312的別名,和GB2312徹底相同。

 

GBK編碼

GBK全稱漢字內碼擴展規範(Chinese Internal Code Specification)。

GBK是GB2312的擴展,除了兼容GB2312外,它還能顯示繁體中文、日文的假名。
GBK採用雙字節表示,整體編碼範圍爲8140-FEFE,首字節在81-FE之間,尾字節在40-FE之間,剔除xx7F一條線。
總計23940個碼位,共收入21886個漢字和圖形符號,其中漢字21003個,圖形符號883個。

 


 

編碼轉換

#include <iconv.h>
iconv_t iconv_open(const char *tocode, const char *fromcode);
size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
int iconv_close(iconv_t cd);

iconv_open 函數聲明要進行哪兩種編碼的轉換,返回一個轉換句柄,供iconv和iconv_close兩個函數使用。

在tocode後面加上字符串"//TRANSLIT",當源字符串中某個字符沒法用目標字符集表示時,能夠選擇一個(或若干個)與該字符最類似的字符來表示;
在tocode後面加上字符串"//IGNORE",當源字符串中某個字符沒法用目標字符集表示時,該字符將被丟棄。

iconv函數將從*inbuf起始的多字節序列轉換到以*outbuf起始的多字節序列,
從*inbuf開始讀取,最多轉換*inbytesleft字節,轉換後,從*outbuf開始寫入,最多寫入*outbytesleft字節。
iconv函數一次轉換一個多字節字符,每次字符轉換後,*inbuf增長已轉換的字節數,*inbytesleft相應減去已轉換的字節數;
對應地,*outbuf和*outbytesleft減小已轉換的字節數。

如下3種狀況轉換失敗:
一、輸入中含無效(invalid)的多字節序列,此時,將errno設置爲EILSEQ,並返回-1,*inbuf指向無效序列的最左端;
二、輸入中以不完整(incomplete)多字節序列做結尾,將errno設置爲EINVAL,並返回-1,*inbuf指向不完整序列的最左端;
三、輸出緩衝區沒有足夠空間存儲下一個字符,此時,將errno設置爲E2BIG,並返回-1。

 

 


 

寬字符類型

char
單字節字符,sizeof(char)=1
char是一種整數類型,在ASCII(7位)範圍內的值,與ASCII對應。
 
 
wchar_t
寬字符,Linux中sizeof(wchar_t)=4
wchar_t沒有規定編碼,在Linux中等價於UTF-32BE。
 
const wchar_t* p = L"abc";          // 前綴L是告訴編譯器,這是寬字符,UTF-8到UTF-32BE
printf("%ls", p);               
編碼爲
0x00000061  0x00000062  0x00000063
 

 

wchar_t有一套本身的讀寫函數,例如:

setlocale(LC_ALL,"zh_CN.UTF-8"); 
wchar_t a[10] = L"你好";
wprintf(L"this is a test !\n"); 
wprintf(L"%d\n",wcslen(a));   
wprintf(L"%ls\n",a);   

 

將utf-8編碼的unicode字符集放入wchar_t中是不明智的,由於,這樣wchar_t中可能有兩個字符的編碼,也可能有一個,也可能不到一個,也可能2個半個。
相關文章
相關標籤/搜索