字元編碼的那些事兒

2021-09-12 10:41:50 字數 3280 閱讀 5867

計算機自己能理解的「語言」是二進位制數,最小的資訊標識是二進位制位,8個二進位制位表示乙個位元組;而我們人類所能理解的語言文字則是一套由英文本母、漢語漢字、標點符號字元、阿拉伯數字等等很多的字元構成的字符集。如果要讓計算機來按照人類的意願進行工作,則必須把人類所使用的這些字符集轉換為計算機所能理解的二級製碼,這個過程就是編碼,他的逆過程稱為解碼。

20世紀60年代初期,美國發布了第乙個計算機字符集--ascii。這個字符集採用7位編碼,定義了包括英文本母大小寫、阿拉伯字母和標點符號,以及33個控制符號等。這些字元被對映到128個8位的二進位制數上,這些數最高位置0,那麼就是0x00~0x7f了,這以方案就是iso-646。

後來隨著計算機的普及,歐洲非英文國家發現美國這套字符集不夠用,於是將最高位也用上了,於是多了128個二進位制值,這一方案是iso8859-1,通常叫做latin-1。

雖然總共有了256個字元,但是對於我們中文來說是遠遠不夠的,後來我們中國弄出了gb2312-1980。這個字符集共收錄了6763個漢字和682個非漢字圖形字元,採用兩個位元組對字元進行編碼,並且它相容ascii編碼方式。後面生僻字、繁體字以及日韓漢字的加入,就有了gbk編碼規範。

這樣大家都是自己的編碼規範是不行的,後面大家統一方式,採用了unicode的最新編碼方式。unicode字符集涵蓋了世界上所有的文字和符號字元,它位每乙個字元指定了統一且唯一的二進位制編碼。注意,unicode只是規定了每個字元的二進位制**,並沒有規定它如何儲存。

由於unicode沒有規定編碼方式,但是如果我們將每個字元都用相同長度的位元組來表示,那將會造成很大的儲存空間浪費。想想,對於ascii最基本的那128個字元,如果都按照最長位元組數來編碼,那些高位的0的空間豈不是都浪費了。這個時候,就推出了utf-8以及utf-16這些編碼方式。

utf-8 是乙個非常驚豔的編碼方式,漂亮的實現了對 ascii 碼的向後相容,以保證 unicode 可以被大眾接受。

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

1、對於單個位元組的字元,第一位設為 0,後面的 7 位對應這個字元的 unicode 碼點。因此,對於英文中的 0 - 127 號字元,與 ascii 碼完全相同。這意味著 ascii 碼那個年代的文件用 utf-8 編碼開啟完全沒有問題。

2、對於需要使用 n 個位元組來表示的字元(n > 1),第乙個位元組的前 n 位都設為 1,第 n + 1 位設為0,剩餘的 n - 1 個位元組的前兩位都設位 10,剩下的二進位制位則使用這個字元的 unicode 碼點來填充。

編碼規則如下:

根據上面編碼規則對照表,進行 utf-8 編碼和解碼就簡單多了。下面以漢字「漢」為利,具體說明如何進行 utf-8 編碼和解碼。

「漢」的 unicode 碼點是 0x6c49(110 1100 0100 1001),通過上面的對照表可以發現,0x0000 6c49 位於第三行的範圍,那麼得出其格式為 1110***x 10****** 10******。接著,從「漢」的二進位制數最後一位開始,從後向前依次填充對應格式中的 x,多出的 x 用 0 補上。這樣,就得到了「漢」的 utf-8 編碼為 11100110 10110001 10001001,轉換成十六進製制就是 0xe6 0xb1 0x89。

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

在了解 utf-16 編碼方式之前,先了解一下另外乙個概念——「平面」。

在上面的介紹中,提到了 unicode 是一本很厚的字典,她將全世界所有的字元定義在乙個集合裡。這麼多的字元不是一次性定義的,而是分割槽定義。每個區可以存放 65536 個(2^16)字元,稱為乙個平面(plane)。目前,一共有 17 個(2^5)平面,也就是說,整個 unicode 字符集的大小現在是 2^21。

最前面的 65536 個字元位,稱為基本平面(簡稱 bmp ),它的碼點範圍是從 0 到 2^16-1,寫成 16 進製就是從 0x0000 到 0xffff。所有最常見的字元都放在這個平面,這是 unicode 最先定義和公布的乙個平面。剩下的字元都放在輔助平面(簡稱 smp ),碼點範圍從 0x010000 到0x10ffff。

基本了解了平面的概念後,再說回到 utf-16。utf-16 編碼介於 utf-32 與 utf-8 之間,同時結合了定長和變長兩種編碼方法的特點。它的編碼規則很簡單:基本平面的字元占用 2 個位元組,輔助平面的字元占用 4 個位元組。也就是說,utf-16 的編碼長度要麼是 2 個位元組(0x0000 到 0xffff),要麼是 4 個位元組(0x010000 到 0x10ffff)。那麼問題來了,當我們遇到兩個位元組時,到底是把這兩個位元組當作乙個字元還是與後面的兩個位元組一起當作乙個字元呢?

這裡有乙個很巧妙的地方,在基本平面內,從 0xd800 到 0xdfff 是乙個空段,即這些碼點不對應任何字元。因此,這個空段可以用來對映輔助平面的字元。

輔助平面的字元位共有 2^20 個,因此表示這些字元至少需要 20 個二進位制位。utf-16 將這 20 個二進位制位分成兩半,前10位對映在0xd800到0xdbff,稱為高位(h),後 10 位對映在0xdc00 到0xdfff,稱為低位(l)。這意味著,乙個輔助平面的字元,被拆成兩個基本平面的字元表示。

因此,當我們遇到兩個位元組,發現它的碼點在0xd800 到 0xdbff 之間,就可以斷定,緊跟在後面的兩個位元組的碼點,應該在0xdc00 到0xdfff 之間,這四個位元組必須放在一起解讀。其實就是下面這張圖。

根據上面的轉換方式,我們就能夠將unicode碼根據utf-16的編碼方式進行轉換。下面我們仍然通過兩個例子來看下:

u+0020,這個值的範圍在第一部分,即經過utf-16編碼後,結果仍然為u+0020,在記憶體中的順序為00 20。

u+12345, 這個值的範圍在第二部分,因此需要先減去0x10000,得到0x02345,拆分成高10位00 0000 1000和低10位11 0100 0101。根據上面規則加上特定值後,高位**值為d808,低位**值為df45,最終記憶體中的順序為d8 08 df 45。

utf-32 是固定長度的編碼,始終占用 4 個位元組,足以容納所有的 unicode 字元,所以直接儲存 unicode 編號即可,不需要任何編碼轉換。浪費了空間,提高了效率。

總結就是,只有 utf-8 相容 ascii,utf-32 和 utf-16 都不相容 ascii,因為它們沒有單位元組編碼。

《深入淺出mysql》

編碼和字符集那些事兒

字符集是人為規定的 相關標準委員會,如iso iec 制定的,將 文字字元 與 數字 一一對應的靜態表。其中,每個 文字字元 對應的數字,也稱為其編碼。例如,ascii碼表就是乙個字符集 字符集字元 編碼ascii a 65 ascii 0 48 字元 character 文字元號 字符集 char...

關於字串的那些事兒

字串,大家都是知道的,但是儲存它的手段卻有很多,不管是char 陣列還是string抑或是 vector,或者這三者的指標都是可以的。那麼我們如何正確的使用它們呢?我通過乙個小專案,期間遇到各種的問題,現在稍微有些眉目。現在記錄一下,以便以後使用,也為大家略盡綿薄之力。char str1 15 ab...

object的那些事兒

昨天和大姐qq。說給我看了,今年找到物件。我就在那裡騙他們。說我找了乙個 人。明年去 以後就沒有多少機會見到他們了。呵呵,大姐就說這樣我不聽話,那樣不聽話,不讓我去,說還借我錢買房子,不能去,去了父母白養這麼大了,我總是騙他們,逗他們。以前還騙他們說我出家。有一段時間,工作不順心,我壓力挺大的。不知...