天天看點

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

ASCII碼

ASCII碼表

計算機中任何資訊都是用0和1來表示的。但是現實世界各種資訊如圖像、視訊、音頻、語言文字卻不是用0和1表示的。為了計算機能夠更加智能,我們需要讓計算機能夠識别人類世界的資訊,是以我們需要将圖像、視訊、音頻、語言文字等資訊轉換成二進制(0和1)存儲到計算機中,這一過程也稱之為編碼。

編碼是資訊從一種形式或格式轉換為另一種形式的過程。

  • 如果隻有一位二進制,那麼可以代表兩種情況,即2^1=2,可能是0或1。
  • 如果有兩位二進制,那麼就可以代表四種情況,即2^2=4,可能是00、01、10或11。
  • ......
  • 如果有七位二進制,那麼就可以代表2^7=128種情況。

在計算機中每八位二進制表示一個位元組(Byte)。早些時候計算機用8位二進制來編碼表示英文字母及一些特殊符号(最前面的一位是0,因為如果八位二進制可以表示2^8=256種情況,如果最前面一位是0那麼就隻能表示2^7=128種情況)。

例如數字字元'9'所代表的二進制是00111001,二進制轉換成十進制是57,轉換成十六進制是39。

例如字母字元'A'所代表的二進制是01000001,二進制轉換成十進制是65,轉換成十六進制是41。

也就是說ASCII碼是一張碼表,在這張表中規定了美國一些常用字元(包括數字字元、字母字元等)所對應的二進制數,它們是一一對應的(也對應了十進制數0到127)。

關于ASCII碼表請參考:ASCII碼表詳解

擴充ASCII碼表

這時候的ASCII碼表僅僅隻支援美國人的日常使用,其他國家人無法用ASCII碼表表示他們國家的語言文字。

是以各個國家将位元組(8位二進制)中最前面未被使用的一位拿來使用,用來表示自己國家的字元符号,那麼用8位二進制來表示狀态就有2^8=256種情況了。比如 é 就被編碼成 130(二進制的 10000010)。

為了保持與ASCII碼的相容性,一般最高位為0的位元組所表示的碼與原來的ASCII碼相同,隻有當最高位為1(1xxx xxxx)的時候才表示各個國家給自己國家添加的128個特殊字元。

但問題來了,前面說的是各個國家對最高位為1之後的128個數字賦予了不同的含義,比如1111 0000這個數字在法國所表示一個字元,但在德國就表示另外一個字元。

可能有多少個國家,擴充ASCII碼就有多少種,如果給一串二進制數,如果想要将其解碼成字元,我們必須知道它是哪個國家的編碼方式,否則就會出現亂碼,也不會解碼成我們想要的内容。

Unicode

再後來,發現這樣不行,如果要對一串未知的二進制進行解碼,你必須知道它的編碼方式。

為了統一所有文字的編碼,産生了Unicode,把所有語言的都統一到一套編碼裡,這樣就不會亂碼了。它為世界上的每個字元都配置設定了唯一的數字編号,可以這樣說,Unicode是一張比ASCII更大的表,它包括了世界上所有國家的所有字元。

Unicode是為了解決傳統字元編碼方案的局限而産生的,為每種語言中的每個字元都設定了統一唯一的二進制編碼,以實作跨語言、跨平台進行文本轉換、處理的要求。

這個編号的範圍是從0x000000到0x10FFFF,每個字元都有一個唯一的 Unicode 編号,這個編号一般寫成 16 進制,在前面加上 U+。例如:“馬”的 Unicode 是U+9A6C。

Unicode僅僅隻是一個字元集,規定了符合對應的二進制代碼,至于這個二進制代碼如何存儲則沒有任何規定。它的想法很簡單,就是為每個字元規定一個用來表示該字元的數字,僅此而已。

以漢字“漢”為例,它的 Unicode 碼點是 0x6c49,對應的二進制數是 110110001001001,二進制數有 15 位,這也就說明了它至少需要 2 個位元組來表示。可以想象,在 Unicode 字典中往後的字元可能就需要 3 個位元組或者 4 個位元組,甚至更多位元組來表示了。

這就導緻了一些問題,計算機怎麼知道你這個 2 個位元組表示的是一個字元,而不是分别表示兩個字元呢?這裡我們可能會想到,那就取個最大的,假如 Unicode 中最大的字元用 4 位元組就可以表示了,那麼我們就将所有的字元都用 4 個位元組來表示,不夠的就往前面補 0。這樣确實可以解決編碼問題,但是卻造成了空間的極大浪費,如果是一個英文文檔,那檔案大小就大出了 3 倍,這顯然是無法接受的。

至于這唯一的數字編号如何對應到二進制表示,有很多種方案:主要有UTF-8、UTF-16、UTF-32。

UTF-32

UTF-32種的32表示32位二進制,四個位元組,是直接将Unicode碼轉換成整數二進制形式。如“漢”字對應的二進制數是0110 1100 0100 1001。

計算機在存儲器種排列位元組有兩種方式:大端法和小端法。

大端法就是将高位位元組放在低位址處,如0x1234,計算機用兩個位元組存儲,一個高位位元組0x12,一個低位位元組0x34。

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系
ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

UTF-32用四個位元組來表示,如果不區分大小端的話,那麼就會出現解讀錯誤,例如拿到四個位元組12 34 56 78,那麼它到底表示的是0x12 34 56 78還是0x78 56 34 12呢,按照不同的端解釋那麼最後所表示的值不一樣。

可以根據他們高低位元組的存儲位置來判斷他們所代表的含義,是以在編碼方式中有 UTF-32BE 和 UTF-32LE,分别對應大端和小端,來正确地解釋多個位元組(這裡是四個位元組)的含義。

UTF-16

UTF-16 使用變長位元組表示 

① 對于編号在 U+0000 到 U+FFFF 的字元(常用字元集),直接用兩個位元組表示。 

② 編号在 U+10000 到 U+10FFFF 之間的字元,需要用四個位元組表示。

同樣,UTF-16 也有位元組的順序問題(大小端),是以就有 UTF-16BE 表示大端,UTF-16LE 表示小端。

UTF-8

UTF-8 是目前網際網路上使用最廣泛的一種 Unicode 編碼方式,它的最大特點就是可變長。它可以使用 1 - 4 個位元組表示一個字元,根據字元的不同變換長度。編碼規則如下:

  • 對于單個位元組的字元,第一位設為 0,後面的 7 位對應這個字元的 Unicode 碼點。是以,對于英文中的 0 - 127 号字元,與 ASCII 碼完全相同。這意味着 ASCII 碼那個年代的文檔用 UTF-8 編碼打開完全沒有問題。
  • 對于需要使用 N 個位元組來表示的字元(N > 1),第一個位元組的前 N 位都設為 1,第 N + 1 位設為0,剩餘的 N - 1 個位元組的前兩位都設位 10,剩下的二進制位則使用這個字元的 Unicode 碼點來填充。

編碼規則如下:

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為什麼二進制是這樣?

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

 下面以漢字"漢"為例,來說明如何進行UTF-8編碼和解碼。"漢"的Unicode碼點是0x6c49,轉換成二進制就是110 1100 0100 1001,通過對照表,發現0x0000 6c49位于第三行的範圍。

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

 那麼得出基本格式是"1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx",接着從"漢"字的二進制數最後一位開始,從後往前依次填充對應格式中的x,多出的x用0補上。這樣最後得到的結果就是"漢"的UTF-8編碼是

11100110 10110001 10001001

,轉換成十六進制就是

0xE6 0xB7 0x89

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

解碼的過程是:如果一個位元組的第一位是0,那麼表示這個位元組對應一個字元;如果一個位元組的第一位是1,那麼連續有多少個1,就表示該字元占用多少個位元組。

由于 UTF-8 的處理單元為一個位元組(也就是一次處理一個位元組),是以處理器在處理的時候就不需要考慮這一個位元組的存儲是在高位還是在低位,直接拿到這個位元組進行處理就行了,因為大小端是針對大于一個位元組的數的存儲問題而言的。

綜上所述,UTF-8、UTF-16、UTF-32 都是 Unicode 的一種實作。

位元組序

位元組序也就是我們常說的大端和小端。

所謂BOM,全稱是Byte Order Mark,它是一個Unicode字元,通常出現在文本的開頭,用來辨別位元組序(Big/Little Endian),除此之外,還可以辨別編碼(UTF-8/UTF-16/UTF-32)。

對于UTF-8/16/32而言,它們名字中的8/16/32指的是編碼機關是多少位的,也就是說,它們的編碼機關分别是8/16/32位,換算成位元組就是1/2/4位元組,如果是多位元組,就要牽扯到位元組序,UTF-8以單位元組為編碼機關,是以不存在位元組序。

UTF- 8編碼的檔案中,BOM占三個位元組。這是個辨別UTF-8編碼檔案的好辦法,軟體通過BOM來識别這個檔案是否是UTF-8編碼,很多軟體還要求讀入的檔案必須帶BOM。

UTF-8也有BOM,但是一般不建議帶上,因為UTF-8沒有大小端,BOM是一個固定的值。

Windows系的軟體儲存的文本檔案,預設都帶有BOM,處理的時候務必注意。

UTF編碼 Byte Order Mark(BOM)
UTF-8 without BOM
UTF-8 with BOM EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF

我們自己來檢視編碼的BOM,建立一個test.txt檔案,内容為"hello world"先設定編碼為UTF-8(也就是UTF-8 without BOM),然後使用WinHex軟體檢視它的十六進制編碼内容。

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

 檢視UTF-8-BOM(也就是UTF-8 with BOM)的頭

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

檢視UTF-16LE的頭

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

  檢視UTF-16BE的頭

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

 檢視UTF-32LE的頭

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

檢視UTF-32BE的頭

ASCII、Unicode和UTF-8、UTF-16、UTF-32的關系

 UTF-16BE、UTF-16LE、UTF-16 三者之間的差別:

  • UTF-16BE,其字尾是BE,即big-endian,是大端的意思,即将高位的位元組放在低位址表示。使用了該編碼後的檔案開頭一定是FE FF。
  • UTF-16LE,其字尾是LE,即little-endian,是小端的意思,即将高位的位元組放在高位址表示。使用了該編碼後的檔案開頭一定是FF FE。
  • UTF-16,沒有指定字尾,即不知道是大端還是小端,是以其開始的兩個位元組表示是大端還是小端,如果是FE FF表示大端,如果是FF FE表示小端,是以如果使用了UTF-16編碼的檔案具體是大端還是小端,由開頭兩個位元組決定。

GBK

GB2312是對ASCII的中文擴充。

GBK編碼:是指中國的中文字元,其它它包含了簡體中文與繁體中文字元,另外還有一種字元“gb2312”,這種字元僅能存儲簡體中文字元。gbk是相容gb2312編碼的。

GBK編碼格式,它的功能少,僅限于中文字元,當然它所占用的空間大小會随着它的功能而減少,打開網頁的速度比較快。

ISO-8859-1

屬于單位元組編碼,最多能表示的字元範圍是0-255(因為一個位元組有八位,即2^8=256),應用于英文系列。比如,字母a的編碼為0x61=97。是20世紀80年代的一個傳統标準,由于隻能代表256個字元,是以隻适用于西方世界的某些語言。即使對于許多支援的語言,也缺少一些字元,更無法表示中文字元。

但是,由于是單位元組編碼,和計算機最基礎的表示機關一緻,是以很多時候,仍舊使用iso8859-1編碼來表示。而且在很多協定上,預設使用該編碼。比如,雖然"中文"兩個字不存在iso8859-1編碼,以gb2312編碼為例,應該是"d6d0 cec4"兩個字元,使用iso8859-1編碼的時候則将它拆開為4個位元組來表示:"d6 d0 ce c4"(事實上,在進行存儲的時候,也是以位元組為機關處理的)。而如果是UTF編碼,則是6個位元組"e4 b8 ad e6 96 87"。很明顯,這種表示方法還需要以另一種編碼為基礎。

參考連結:

  • 你真的懂 Unicode 和 UTF-8 是什麼關系嗎?來看看這個就徹底懂了!
  • 徹底弄懂 Unicode 編碼
  • UNICODE,GBK,UTF-8差別