1. 程式人生 > >從字元編碼的發展歷史理解ANSI、GB2312、Unicode、UTF8和UTF16區別

從字元編碼的發展歷史理解ANSI、GB2312、Unicode、UTF8和UTF16區別

        在程式設計學習的深入後,不可避免的會遇到ANSI、GB2312、UTF8的編碼問題,如果不徹底瞭解他們的區別,都最終會造成一個問題--亂碼!例如檔案開啟亂碼,資料庫亂碼、網頁亂碼等等各種亂碼,這裡就通過字元編碼發展歷史的介紹來分清各種不同的編碼。

一、ASCII碼

        我們都知道計算機起源於美國,早期的計算機只是用於科學計算,但是在計算機迅速發展時,計算機被要求不僅僅能夠進行數值計算,還要進行字元處理和表示。於是一套名為ASCII(American Standard Code for Information Interchange,美國資訊互換標準程式碼)碼的編碼方式被創造而出,使用7位(bit)來表示一個字元,總共能夠表示128種字元。

        這套編碼包含了控制碼(例如\n換行符等)、符號(?!等)、數字和26個英文字母包括其大小寫。但是由於這是一套由美國人提出的美國字元編碼所以並沒有包含中文、韓文等等其他國家的語言,畢竟當時誰也沒想到計算機會普及到全世界。

後來IBM對這套ASCII碼進行了擴充,使用8位來表示一個字元,新增了128種字元,這也僅僅是對一些拉丁字母和特殊符號的擴充。

二、ANSI

        當計算機普及到全世界時,各個國家面臨的首要問題就是要針對自己國家的語言制定一套自己國家的編碼規範,我國就提出裡一套針對中文的GB2312的編碼方式,這套編碼方式基於ASCII碼(並非IBM的ASCII擴充版本),使用2個位元組表示一個漢字,具體的方式是前127個字元不變。當第一個位元組(高位元組)大於160的時候,表示一個漢字的開始,再用這個位元組組合第二個位元組(低位元組,範圍也是160-255)共同表示一個漢字。在這套編碼方式中,不僅把中文編碼進去,還把一些數學符號、羅馬希臘字母和日本假名等等都編碼進去,並且還把ASCII中原有的26個英文字母和符號都編入,當然這些字母是以2個位元組表示,為和ASCII中原有的字母區別表示,稱前者為“全形字元”,後者為“半形字元”。

        當然我大中華文化底蘊深厚,GB2312也只能編入部分常用漢字,為了把更多的漢字編入進來,針對GB2312進行擴充,就創造出了GBK標準。GBK只要求當高位元組大於127時就表示漢字的開始,低位元組也不再要求範圍。

        類似我國的編碼方案,其他地區和國家也制定了自己的編碼方案,如日本的Shift_JIS等等。這些編碼方案稱為 "DBCS"(Double Byte Charecter Set 雙位元組字符集)"即是用雙位元組表示一個字元,也稱為ANSI。這裡的ANSI代表了不同國家的不同編碼方案,如果一臺Windows作業系統設定為中文,那麼ANSI就表示GBK,如果設定為日本ANSI就表示Shift_JIS。ANSI雖然能夠表示全世界的字元,但是產生兩個比較麻煩的問題。

1、一段使用ANSI編碼的字串,有中文有英文,如何統計共有多少字元?這下麻煩了,如果是單純的英文,sizeof一下即可,但是有中文混雜其中則必須便利一遍並做判斷,十分麻煩

2、也是最麻煩的一個問題,使用ANSI編碼的一篇文章,被臺灣友人拿去,開啟後,全是亂碼,因為臺灣地區使用的是BIG5碼,不同的地區編碼雖然都稱作ANSI,但是互相之間沒有演算法做出轉換,這大大影響了阻礙各地區、國家之間的交流

(中文編碼詳細內容請檢視我的部落格【中文編碼-區位碼、國標碼、內碼區別】)

三、Unicode

        為了統一全世界的文字編碼,ISO(國際標準化組織)制定了一種新的編碼規範,這種編碼規範將全世界的文字放在一張表內,稱它為"Universal Multiple-Octet Coded Character Set",簡稱 UCS, 俗稱"UNICODE"。請注意,Unicode和GBK、BIG5等ANSI編碼仍然沒有一種直接的演算法進行轉換,Unicode在制定時可以看成是廢了所有的地區性編碼,重新制定的編碼,也不是從ASCII繼承而來(但是前128字元仍保留為ASCII字元)。嚴格意義上說Unicode只是一套標準,為全世界文字給予一個唯一的編碼,但是並沒有規定在計算機中如果儲存。所以根據Unicode標準來制定具體的實施方案就是UTF-8,UTF16以及UTF32它們規定了如何在計算機中儲存、表示。

        先說UTF-16,對於Unicode標準中的文字,使用2個位元組(16位)或4個位元組來儲存其編號。為什麼是2個或4個位元組表示呢?因為如果要包含全世界所有的符號文字,即使使用2個位元組都無法包含,想想GBK編碼,佔滿了2個位元組僅僅包含我們大中華文字部分文字。但是針對各國的常用文字在2個位元組內都能包含,所以通常使用2個位元組的UTF16就足夠了。所以在使用UTF16編碼的文章或文件裡,統計字元個數無需像ANSI那樣麻煩,而且同一篇文章在各國的Windows等作業系統中使用UTF8開啟不會造成亂碼問題,十分方便。其實通常所說的Unicode指的就是UTF16

        UTF8,對於歐美的一些國家,常用的還是英文字母,如果使用UTF16,白白浪費一倍的儲存空間(因為他們只需前128的字元就足夠了)。針對此,UTF8產生了,它是一種變長編碼方式,使用8位來實現Unicode中的所有字元,具體如何實現呢?

程式將一個位元組一個位元組的來讀取,然後再根據每個位元組中開頭的標誌來識別是該把一個還是兩個或三個位元組做為一個字元來處理.

(xx表示任意bit)

0xxxxxxx,以0開頭表示把一個位元組做為一個單元.就跟ASCII完全一樣.

110xxxxx 10xxxxxx.如果是這樣的格式,則把兩個位元組當一個單元

1110xxxx 10xxxxxx 10xxxxxx 如果是這種格式則是三個位元組當一個單元.

也就是說UTF8編碼的字元可能是1位元組,可能是2位元組也可能是3位元組,數一下3位元組中x的個數,16個,其最大的字元個數2的16次方,和UTF16相同。

         我們可以看出UTF-8需要判斷每個位元組中的開頭標誌資訊,所以如果一當某個位元組在傳送過程中出錯了,就會導致後面的位元組也會解析出錯.而UTF-16不會判斷開頭標誌,即使錯也只會錯一個字元,所以容錯能力強.

         但是由於UTF8對於一篇文章,佔用的空間可能比UTF16小,而在在網路傳輸中由於是一個一個位元組傳輸的,無需判斷大端小端問題,所以UTF8在網路傳輸中十分普及。

UTF32,4個位元組表示一個文字,普及率較UTF8和UTF16較小。(大端小端問題請檢視我的部落格裡【大端小端模式詳解】)

附帶:BOM標記

一篇文章儲存在記憶體中,使用記事本等工具開啟時,記事本是怎麼知道這篇文章使用什麼編碼方式呢?就是靠的位於文章最開頭的BOM標記

前面說了要知道具體是哪種編碼方式,需要判斷文字開頭的標誌,下面是所有編碼對應的開頭標誌(大端小端一般常見於網路傳輸中)

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-X(轉自StackFlow):

·        UTF8: Variable-width encoding(變長編碼), backwards compatible with ASCII. ASCIIcharacters (U+0000 to U+007F) take 1 byte, code points U+0080 to U+07FF take 2bytes, code points U+0800 to U+FFFF take 3 bytes, code points U+10000 toU+10FFFF take 4 bytes. Good for English text, not so good for Asian text.

·        UTF16: Variable-width encoding(變長編碼). Code points U+0000 to U+FFFF take 2bytes, code points U+10000 to U+10FFFF take 4 bytes. Bad for English text, goodfor Asian text.

·        UTF32: Fixed-width encoding(定長編碼). All code points take 4 bytes. Anenormous memory hog, but fast to operate on. Rarely used.

四、編碼問題在程式語言中

        在C\C++中由於其誕生之時並沒考慮語言編碼問題,所以預設都使用DBCS方式編碼,可以手動設定為Unicode,它們可以定義Unicode型別的字串,但是型別需要使用wchar,本質只是unsigned short型別。

        而誕生比較晚的語言,如C#等語言,都是原生支援Unicode方式,具體使用UTF16形式編碼(string型別只能為UTF16),一個char型別就佔用2位元組。