聯通不如移動的故事
在編碼界一直流傳著聯通不如移動的一個故事。。。
請不要誤會,聯通和移動和本篇文章所說的編碼確實沒什麼關係,但請出聯通和移動幫忙做個小實驗,再來仔細說說編碼。
在Windows系統下,在桌面上右鍵新建一個記事本檔案,開啟它輸入“聯通”兩個漢字,Ctrl+S儲存並關閉。
雙擊再次開啟它,看到了什麼?奇怪,文字怎麼變成亂碼了?
好吧,再次新建一個檔案,這回輸入“移動”儲存再試試。神奇,移動居然完美顯示。
好了,不說什麼故事了,這個有趣的現象正是為了聊聊計算機中“編碼”的那些事,之後再解釋為什麼“聯通不如移動”。
聊聊字元編碼的發展史
在計算機中,所有儲存的資料都由二進位制表示。字母、數字、字元這些都不例外,計算機中最小的單位就是二進位制位(0和1),8個位表示一個位元組,因此8個二進位制位就可以排列組合出256種狀態,也就是理論上可以表示出256種字元,而由哪些二進位制位表示哪些字元,這就是由人來決定的了,也就是人們制定出的各種“編碼”。
電腦這種東西最早由老外發明,外國人使用的英語只有26個字母,再加上標點、數字和一些符號也不會太多,因此英文通常用ASCII編碼來表示。
ASCII碼
ASCII碼最開始只在美國使用,組合出的256種狀態中,第0~32中規定了特殊用途,一旦終端、印表機遇上約定好的這些位元組被傳過來時,就要做一些約定的動作,比如遇到0×10, 終端就換行等等。
又把所有的空格、標點符號、數字、大小寫字母分別用連續的位元組狀態表示,一直編到了第 127 號,這樣計算機就可以用不同位元組來儲存英語的文字了。
記得當初學習C語言的時候,就清楚的知道了一些常用的ASCII碼值,比如大寫A是65,小寫a是97等。
這128個符號(包括32個不能列印出來的控制符號),只佔用了一個位元組的後面7位,最前面的一位統一規定為0。
英文可以表示了,但是世界上除了英文還有很多語言。我們的中文文字浩如煙海,僅僅靠這8個二進位制位遠遠不夠,怎麼辦?
GB2312
且不說中文,在歐洲有些國家的語言中也有一些特殊的字母,比如俄文希臘文等。於是便使用127號之後的空位繼續表示他們的字母。當然,由於每個國家的語言不同,就越來越亂,比如130在法語中是字母 é,但是在希伯萊語中130卻是他們的字母 ג。
我們的中文就更難辦了,即使把所有的位都用上,也表示不完成千上萬的漢字,於是我們自己也制定了一套中文的編碼GB2312。
中國為了表示漢字,把127號之後的符號取消了,規定:
- 一個小於127的字元的意義與原來相同,但兩個大於 127 的字元連在一起時,就表示一個漢字;
- 前面的一個位元組(他稱之為高位元組)從0xA1用到0xF7,後面一個位元組(低位元組)從 0xA1 到 0xFE;
- 這樣我們就可以組合出大約7000多個(247-161)*(254-161)=(7998)簡體漢字了。
- 還把數學符號、日文假名和ASCII裡原來就有的數字、標點和字母都重新編成兩個字長的編碼。這就是全形字元,127以下那些就叫半形字元。
把這種漢字方案叫做 GB2312。GB2312 是對 ASCII 的中文擴充套件。
GBK
再後來,發現了GB2312雖然解決了中文編碼的問題,但是仍有不足。
GB2312表示的中文有時不夠,有些字並不是生僻字,但是沒有收錄其中,當時有個小插曲,我當時在高考報名的系統中查詢成績的時候報不出我的名字,只能報出我的姓,正是因為我的名字“玥”字不在GB2312的編碼範圍,因此沒有。
於是乾脆不再要求低位元組一定是 127 號之後的內碼,只要第一個位元組是大於 127 就固定表示這是一個漢字的開始,又增加了近 20000 個新的漢字(包括繁體字)和符號。
這就是更全面的GBK編碼。
Unicode
隨著發展,每個國家都對自己的語言編出一套自己的編碼,真是混亂不堪,我們不知道別人用什麼編碼,別人也不知道我們用什麼編碼,於是標準組織出手了。
ISO標準組織看到了亂象,制定了一套Unicode編碼以解決這種混亂的局面,它的制定簡單粗暴,不是全世界的語言多麼,我乾脆就規定,所有的字元都給我用兩個位元組表示(兩個8位一共16位),對於 ASCII 裡的那些 半形字元,Unicode 保持其原編碼不變,只是將其長度由原來的 8 位擴充套件為16 位,而其他文化和語言的字元則全部重新統一編碼。
從 Unicode 開始,無論是半形的英文字母,還是全形的漢字,它們都是統一的一個字元。同時,也都是統一的兩個位元組。
UTF8
Unicode的制定是在1990年,正式使用在1994年,那個年代在現在來看簡直是遠古時期,那時由於網際網路並不發達並沒有推廣開。
隨著網際網路的發展,為了解決Unicode傳輸問題,於時面向眾多的UTF標準出現了。
- UTF-8 就是在網際網路上使用最廣的一種 Unicode 的實現方式
- UTF-8就是每次以8個位為單位傳輸資料
- 而UTF-16就是每次 16 個位
- UTF-8 最大的一個特點,就是它是一種變長的編碼方式
- Unicode 一箇中文字元佔 2 個位元組,而 UTF-8 一箇中文字元佔 3 個位元組
- UTF-8 是 Unicode 的實現方式之一
因為UTF8是Unicode的實現方式之一,它們之間是互通的,就是說Unicode編碼可以傳換為UTF8,它有一套對應規則:
Unicode符號範圍(16進位制) | UTF8編碼(2進位制) |
---|---|
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 |
可以看到,對於單位元組的符號,位元組的第一位設為0,後面7位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的(見上面表格的第一行)。
對於n位元組的符號(n>1),第一個位元組的前n位都設為1,第n+1位設為0,後面位元組的前兩位一律設為10。剩下的沒有提及的二進位制位,全部為這個符號的 Unicode 碼。
說的有些抽象,舉個例子吧,比如來了一個漢字,電腦是怎麼知道的它是用UTF8編碼的呢?
因為漢字用三個位元組表示(別再問為什麼用三個位元組表示了,這是規定),因此第一個位元組的前三位都為1,第四位設為0,後面的位都以10開頭,所以它肯定長這個樣子:1110xxxx 10xxxxxx 10xxxxxx。
OK,電腦按照這個規則一看明白了,來的是個漢字!
不如再舉個例子,從Unicode編碼表中查出一個漢字對應的編碼,把它轉換為UTF8試一試,就用我的名字“玥”字吧,它的Unicode編碼為u73a5
首先第一步把16進位制轉換為2進位制,它的值是111001110100101
,那怎麼拆分這個2進位制的值呢?因為UTF8都是後6位為這個字元的Unicode的碼,所以我們從右往左數6位給一一對應上,不足的位補0就好了。
這樣就得出了“玥”字的UTF8編碼:11100111 10001110 10100101
作為開發人員完全可以用程式碼實現一下,這裡用node.js真實的實現一下轉碼:
function transferToUTF8(unicode) {
code = [1110, 10, 10];
let binary = unicode.toString(2); //轉為二進位制
code[2] = code[2] + binary.slice(-6); //提取後6位
code[1] = code[1] + binary.slice(-12, -6); //提取中間6位
code[0] = code[0] + binary.slice(0, binary.length - 12).padStart(4, `0`); //取剩餘開始的位,不夠補0
code = code.map(item => parseInt(item, 2)); //把字串轉換為二進位制數值
return Buffer.from(code).toString(); //利用Buffer轉轉為漢字
}
console.log(transferToUTF8(0x73a5));
複製程式碼
執行結果:
玥
複製程式碼
以上程式碼定義了一個transfer函式,引數接收一個16進位制值,它代表了一個Unicode字元,transfer函式內部先轉換為二進位制,並按照UTF-8的規則轉換為相應的UTF-8編碼,最後,利用node.js的Buffer最終轉碼成漢字,可以看到,已經正確輸出了漢字“玥”。
以上,就是簡單分析了Unicode和UTF-8的轉換關係。
為什麼聯通不如移動?
故事就要講完了,說了這麼多編碼的事現在可以回頭看看開篇為什麼聯通變成了亂碼,因為在Windows的記事本中文預設的儲存編碼為GB2312,通過查詢可以查到漢字“聯”對應的GB2312編碼為uc1aa,轉換為二進位制是1100000110101010
,正好是16位兩個位元組,按8位拆成兩組正好與UTF8的第二種編碼格式對應上了:110xxxxx 10xxxxxx
,這樣再次開啟記事本的時候Windows掃描檔案內容,它就會認為這是UTF-8編碼的檔案,而不是GB2312!此時此刻按照UTF-8來解析檔案內容當然出現了亂碼。
這時可以重新另存為檔案,把檔案格式改為GB2312來儲存,現次開啟“聯通”終於顯示了。
這個例子很極端,可以說“聯通”二字的編碼正好是個巧合,但是搞明白了編碼的細節,更有助於我們在開發中遇到問題可以快速理解其實質,並加以解決,在此記下筆記,與大家共同學習提高。