一文了解字符編碼知識

字符編碼的奧祕

本文旨在講解常見的字符編碼,如:UnicodeUTF-8GBK字符集,以及emoji

起初計算機在美國發明,自然大家考慮的是如何表示英文,英語字母總共 26 個,加上特殊字符,用 128 個字符,一個byte即足以表示出來。這個就是大家所熟知的ASCII編碼。對應關係很簡單,一個字符對應一個byte

但很快人們發現,其他非英語國家的文字遠遠超過ASCII碼,不同國家推出了自己不同的編碼方式,中國的gb2312就是我們國家自己推行的編碼方式,這樣下去每個國家都有自己的編碼方式,來回轉換太麻煩了;這時候大家當然想統一字符編碼,這時候出現了新的編碼方式,unicode編碼方式,將編碼統一,規定了每個字符對應的unicode碼。

Unicode 概述

Unicode(統一碼,萬國碼)是基於通用字符集(Universal Character Set)的標準發展。它爲每種語言中的每個字符設定了統一併且唯一的二進制編碼,以滿足語言、跨平臺進行文本轉換、處理的要求。

Unicode是 1988 年由 Apple 和 Xerox 共同建立的一項標準。1991 年,成立專門的協會來開發和推動Unicdoe

Unicode用數字 0 到 0X10FFFF 來映射這些數字,最多容納 1114112 個字符。1114112 是怎麼計算出來的?將 0X10FFFF 分成 0X10 和 0XFFFF 兩部分。我們知道 0XFFFF 是 65535,那麼 [0,65535] 區間內,總共是 65536 個。同理,0X10 用 10 進製表示爲 16,那麼 [ 0,16 ] 左右閉區間,總共是 17 個(我們稱之爲 17 個平面)。所以 17 乘以 65536 = 1114112。

這裏需要注意:第 0 平面最爲重要,後面第 1,2…16 所有平面都是通過第 0 平面中的一個代理區表示的(詳見圖 4)。

世界上各種字符,用 Unicode 排布

當我們看到 0X10FFFF 的時候,會產生一個疑問。怎麼是三個字節呢?現代計算機都是 32 位或者 64 位了吧。如果是 32 位的話,第一個字節幹什麼用,是全用 0 填充麼?怎麼第二個字節也就用到 0x10?

前面我們提到 “通用字符集”。目前世界上有兩個標準UCS-2用 2 個字節編碼和UCS-4用 4 個字節編碼。那麼 0X10FFFF 即UCS-4了。

把 0x10FFFF 用二進制表示:0000-00000001-00001111-11111111-1111

如上所示,UCS-4根據最高位爲 0 的最高字節分成 2^7=128 個 group。每個 group 再根據次高字節分爲 256 個平面(plane)。每個平面根據第 3 個字節分爲 256 行 (row),每行第 4 字節有 256 個碼位(cell)。每個平面有 2^16=65536 個碼位。如圖 1:

(圖 1)

上面我們講過,14-15-16 平面基本都是PUA,最重要的是下面的 0-1-2 三個平面,特別是第 0 個平面,也就是下圖中色彩斑斕的平面;

(圖 2)

圖中每層代表一個平面。Unicode計劃使用了 17 個平面,一共有 17*65536=1114112 個碼位。在Unicode 5.0.0版本中,已定義的碼位只有 238605 個,分佈在平面 0、平面 1、平面 2、平面 14、平面 15、平面 16。其他平面尚未使用,如下圖所示:

(圖 3)

其中,平面 15 和 16 上定義了兩個各佔 65534 個碼位的專用區(Private Use Area),分別是 0xF0000-0xFFFFD 和 0x100000-0x10FFFD。所謂專用區,就是保留給大家放自定義字符的區域,可以簡寫爲PUA。下圖說明了第 0 平面的字符分佈:

(圖 4)

平面 0 也有一個專用區:0xE000-0xF8FF,有 6400 個碼位。平面 0 的 0xD800-0xDFFF,共 2048 個碼位(見上圖),是一個被稱作代理區(Surrogate)的特殊區域。代理區的目的是:用兩個UTF-16字符表示BMP以外的字符。如前所述在Unicode 5.0.0版本中,238605-65534*2-6400-2048=99089。餘下的 99089 個已定義碼位分佈在平面 0、平面 1、平面 2 和平面 14 上,它們對應着Unicode目前定義的 99089 個字符。平面 0、平面 1、平面 2 和平面 14 上分別定義了 52080、3419、43253 和 337 個字符。中國漢字的Unicode位置,71226 個漢字分別分佈在兩個平面上,第 0 個平面上有 27973 個常用漢字,第 2 個平面 43253 個漢字,你會發現第 2 個平面上清一色都是漢字。

UTF-8 編碼

回到本文的重點UTF-8編碼,utf-8Unicode的一種存儲、傳輸方式,是一種可變長編碼方式;是Unicode實現方式之。UTF是 “Unicode/UCS Transformation Format” 的首字母縮寫。Unicode是字符集也是編碼集,UTF-8是編碼集;UTF-8以字節爲單位對Unicode進行編碼。它是可變長的編碼方式的從表 1 中也能開出來。從UnicodeUTF-8的編碼方式如下:

(圖 5)

例:“漢” 字的Unicode編碼是 0x6C49。0x6C49 在 0x0800-0xFFFF 之間,使用用 3 字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將 0x6C49 寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的 x,得到:11100110 10110001 10001001,即 E6 B1 89。因爲,常用漢字第一個的編碼從 0X3e00,所以常用漢字的UTF8都是三個字節。如果在平面 2 中的漢字必須 4 個字節了。如果你用 C 語言做過協議棧,就會清楚記得,給漢字開闢內存時候要使用 4 個字節。

此外,使用正則表達式來區分漢字,通常使用下面的代碼:

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"[\u4e00-\u9fa5]"];
**_判斷是否爲中文的正則表達式_**
if([predicate evaluateWithObject:name]){
   是中文
}else{
   不是中文
}

那麼對於一些生僻字,就不靈了,如下圖:

(圖 6)

如四個日組成的漢字,運行結果卻 “不是漢字”,因爲它的Unicode編碼是 0xD84C-DEAB(這是代理區)。不在 [e4E00-9FA5] 區間。但,此正則表達式對於鑑別常用漢字已經是足夠使用了。

代理區

第 0 平面中,0xD800-0xDFFF,共 2048 個碼位用於表示代理區。在輸入框輸入一個emoji——😊微笑,然後通過UTF-8轉化工具,看看它的編碼是這樣:

(圖 7)

可以看到Unicode編碼是 D83D-DE03。utf-8的編碼是 F09F-9883。常見字符Unicode很少四個字節的,這個不尋常的。通常utf-8是 1 到 3 個字節的,也就是說在Unicode編碼空間的第 0 個平面上。可以簡單推測:既然emojiutf-8是 4 個字節,說明在 1,2…16 個平面中的某一個 我們再看看 “微笑” 的emoji符號的Unicode:D83D-DE03,已經超過了表格中最大的 0X10FFFF 了,怎麼回事???下面我們根據utf-8的值:F09F-9883. 來反推Unicode對應的數值吧,看看究竟是爲什麼:

(圖 8)

得出的結果是 0x1-F603,這個結果跟Unicode:D83D-DE03 的值相差很大,所以,中間肯定經過了一些轉換步驟,這個轉換就是utf-16的代理!!!

UTF-16

UTF是 “Unicode/UCS Transformation Format” 的首字母縮寫,即把Unicode字符轉換爲某種格式之意。

正常情況下,一個Unicode兩個字節,在轉化uft-8的時候,根據協議,兩個兩個字節,對應一個uft-8這樣完成轉化或者稱爲映射!

其實在第 0 個平面中,專門有一個代理區域,不表示任何字符,只用於指向第 1 到第 16 個平面中的字符,這段區域是:D800——DFFF。其中 0xD800——0xDBFF 是前導代理 (lead surrogates),0xDC00——0xDFFF 是後尾代理 (trail surrogates)。

一個代理對兒(前導,後尾),就表示一個utf-16的字符。就那emoji的微笑來說,前導是代理:D83D;後尾代理是:DE03。根據下圖可以得出utf-16的值是:0x1-F603。這就照應上了。

具體的公式是:0x10000 + (前導 - 0xD800) * 0x400 + (後導 - 0xDC00) =utf-16編碼。

(圖 9)

筆者做一個形象的比喻:這對兒(前導代理,後尾代理)就像一個指針,指向了第 1——16 平面上的每一個碼位。經過計算,不難得出:16 個平面 * 每個平面碼位 65536 = 1,048,576 個,前導 X 後尾代理,可以表示的碼位也是 1,048,576 個(哈!真是一個完美的解決方案)如圖 9 所示。

if(Unicode第一個字節 >=0xD8 && Unicode <=0xDB){
    這是代理區域,表示第1——16平面的字符。每四個字節表示一個單元
}
else{
    這是正常映射區域,表示第0個平面。每兩個字節表示一個單元。
}

Emoji並不都是四個字節。舉個例子有些複雜表情會更長:比如👩‍👩‍👦‍👦,使用NSString標示時候:str.length 是 11,data.length是 25:他是由四個emoji組成的。只不過在編碼的開始加入了特殊的控制字符。👩‍👩‍👦‍👦=👩+👩+👦+👦

NSString* str = @“‍‍‍👩‍👩‍👦‍👦”;
NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@“emoji str-len:%d data-len:%d”,str.length,data.length);

結果是:emoji str-len:11 data-len:25

詳細查看網站 https://apps.timwhitlock.info/unicode/inspect

iOS 上實現顯示漢字的 Unicode 和 UTF8 代碼

主要思想:

使用NSString的方法dataUsingEncoding,參數可以選擇NSUnicodeStringEncodingNSUTF8StringEncoding然後得到NSData

如果用GBK需要自己生成一個NSStringEncoding,代替NSUTF8StringEncoding

   NSStringEncoding gbkEncoding =CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);

NSData包含了首地址和長度。比如 “聯通” 經過Unicode後是 4 個字節,UTF-8之後是 6 個字節,都可以從NSData中得到。然後,用char*指向NSSdata後,逐個將char打印出來就行。如下圖所示:

(圖 10)

注意:由於 intel cpu 是採用小端,所以如果不進行大端轉換,視覺上是顛倒的,這裏我已經給轉成大端的了,大端即正常視覺模式。小故事:早期 window 電腦用記事本存儲 “聯通” 兩個字,關閉再打開,顯示亂碼。原因就是 windows 默認保存的編碼是ANSI(在中國是GB2312),而 “聯”ANSI編碼是  0xC1AA 二進制排列是 1100 0001  1010 1010;“通”ANSI編碼是  0xCDA8 二進制排列是 1100 1101  1010 1000;開頭是 110 開頭,再次打開記事本,系統會使用utf8打開。就會異常。現在的操作系統會存儲一個BOM,來表明是那種編碼避免了亂碼的問題。

GBK 簡單介紹

GBK——專門爲解決漢字的編碼而生成的解決方案。那麼,一個漢字究竟被存儲爲什麼,就需要:先查unicode碼錶,然後根據在碼錶的位置進行計算。例如:“電” 字,在碼錶中是 3575,而在GB2312的碼錶中爲 B5E7。GBK的中文編碼是雙字節來表示的,英文編碼是用ASCII碼錶示的,即用單字節表示。但GBK編碼表中也有英文字符的雙字節表示形式,所以英文字母可以有 2 種GBK表示方式。爲區分中文,將其最高位都定成 1。英文單字節最高位都爲 0。當用GBK解碼時,若高字節最高位爲 0,則用ASCII碼錶解碼;若高字節最高位爲 1,則用GBK碼錶解碼。

編碼方式:

字符有一字節和雙字節編碼,00–7F 範圍內是第一個字節,和ASCII保持一致,此範圍內嚴格上說有 96 個文字和 32 個控制符號。之後的雙字節中,前一字節是雙字節的第一位。總體上說第一字節的範圍是 81–FE(也就是不含 80 和 FF),第二字節的一部分領域在 40–7E,其他領域在 80–FE。具體來說,定義的是下列字節:

(圖 11)

雙字節符號可以表達的 64K 空間如下圖所示。綠色和黃色區域是GBK的編碼,紅色是用戶定義區域。沒有顏色的區域是不正確的代碼組合:

(圖 12)

要點總結

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/fVbQyQ2D2eQo0U5HQmbxRg