從乙個小故事聊聊字元編碼那些事

2021-09-13 10:53:41 字數 4082 閱讀 2971

在編碼界一直流傳著聯通不如移動的乙個故事。。。
請不要誤會,聯通和移動和本篇文章所說的編碼確實沒什麼關係,但請出聯通和移動幫忙做個小實驗,再來仔細說說編碼。

在windows系統下,在桌面上右鍵新建乙個記事本檔案,開啟它輸入「聯通」兩個漢字,ctrl+s儲存並關閉。

雙擊再次開啟它,看到了什麼?奇怪,文字怎麼變成亂碼了?

好吧,再次新建乙個檔案,這回輸入「移動」儲存再試試。神奇,移動居然完美顯示。

好了,不說什麼故事了,這個有趣的現象正是為了聊聊計算機中「編碼」的那些事,之後再解釋為什麼「聯通不如移動」。

在計算機中,所有儲存的資料都由二進位制表示。字母、數字、字元這些都不例外,計算機中最小的單位就是二進位制位(0和1),8個位表示乙個位元組,因此8個二進位制位就可以排列組合出256種狀態,也就是理論上可以表示出256種字元,而由哪些二進位制位表示哪些字元,這就是由人來決定的了,也就是人們制定出的各種「編碼」。

電腦這種東西最早由老外發明,外國人使用的英語只有26個字母,再加上標點、數字和一些符號也不會太多,因此英文通常用ascii編碼來表示。

ascii碼最開始只在美國使用,組合出的256種狀態中,第0~32中規定了特殊用途,一旦終端、印表機遇上約定好的這些位元組被傳過來時,就要做一些約定的動作,比如遇到0×10, 終端就換行等等。

又把所有的空格、標點符號、數字、大小寫字母分別用連續的位元組狀態表示,一直編到了第 127 號,這樣計算機就可以用不同位元組來儲存英語的文字了。

記得當初學習c語言的時候,就清楚的知道了一些常用的ascii碼值,比如大寫a是65,小寫a是97等。

這128個符號(包括32個不能列印出來的控制符號),只占用了乙個位元組的後面7位,最前面的一位統一規定為0。

英文可以表示了,但是世界上除了英文還有很多語言。我們的中文文字浩如煙海,僅僅靠這8個二進位制位遠遠不夠,怎麼辦?

且不說中文,在歐洲有些國家的語言中也有一些特殊的字母,比如俄文希臘文等。於是便使用127號之後的空位繼續表示他們的字母。當然,由於每個國家的語言不同,就越來越亂,比如130在法語中是字母 é,但是在希伯萊語中130卻是他們的字母 ג。

我們的中文就更難辦了,即使把所有的位都用上,也表示不完成千上萬的漢字,於是我們自己也制定了一套中文的編碼gb2312。

中國為了表示漢字,把127號之後的符號取消了,規定:

把這種漢字方案叫做 gb2312。gb2312 是對 ascii 的中文擴充套件。

再後來,發現了gb2312雖然解決了中文編碼的問題,但是仍有不足。

gb2312表示的中文有時不夠,有些字並不是生僻字,但是沒有收錄其中,當時有個小插曲,我當時在高考報名的系統中查詢成績的時候報不出我的名字,只能報出我的姓,正是因為我的名字「玥」字不在gb2312的編碼範圍,因此沒有。

於是乾脆不再要求低位元組一定是 127 號之後的內碼,只要第乙個位元組是大於 127 就固定表示這是乙個漢字的開始,又增加了近 20000 個新的漢字(包括繁體字)和符號。

這就是更全面的gbk編碼。

隨著發展,每個國家都對自己的語言編出一套自己的編碼,真是混亂不堪,我們不知道別人用什麼編碼,別人也不知道我們用什麼編碼,於是標準組織出手了。

iso標準組織看到了亂象,制定了一套unicode編碼以解決這種混亂的局面,它的制定簡單粗暴,不是全世界的語言多麼,我乾脆就規定,所有的字元都給我用兩個位元組表示(兩個8位一共16位),對於 ascii 裡的那些 半形字元,unicode 保持其原編碼不變,只是將其長度由原來的 8 位擴充套件為16 位,而其他文化和語言的字元則全部重新統一編碼。

從 unicode 開始,無論是半形的英文本母,還是全形的漢字,它們都是統一的乙個字元。同時,也都是統一的兩個位元組。

unicode的制定是在2023年,正式使用在2023年,那個年代在現在來看簡直是遠古時期,那時由於網際網路並不發達並沒有推廣開。

隨著網際網路的發展,為了解決unicode傳輸問題,於時面向眾多的utf標準出現了。

因為utf8是unicode的實現方式之一,它們之間是互通的,就是說unicode編碼可以傳換為utf8,它有一套對應規則:

unicode符號範圍(16進製制)

utf8編碼(2進製)

0000 0000-0000 007f

0******x

0000 0080-0000 07ff

110***xx 10******

0000 0800-0000 ffff

1110***x 10****** 10******

0001 0000-0010 ffff

11110*** 10****** 10****** 10******

可以看到,對於單位元組的符號,位元組的第一位設為0,後面7位為這個符號的 unicode 碼。因此對於英語字母,utf-8 編碼和 ascii 碼是相同的(見上面**的第一行)。

對於n位元組的符號(n>1),第乙個位元組的前n位都設為1,第n+1位設為0,後面位元組的前兩位一律設為10。剩下的沒有提及的二進位制位,全部為這個符號的 unicode 碼。

說的有些抽象,舉個例子吧,比如來了乙個漢字,電腦是怎麼知道的它是用utf8編碼的呢?

因為漢字用三個位元組表示(別再問為什麼用三個位元組表示了,這是規定),因此第乙個位元組的前三位都為1,第四位設為0,後面的位都以10開頭,所以它肯定長這個樣子:1110***x 10****** 10******。

ok,電腦按照這個規則一看明白了,來的是個漢字!

不如再舉個例子,從unicode編碼表中查出乙個漢字對應的編碼,把它轉換為utf8試一試,就用我的名字「玥」字吧,它的unicode編碼為\u73a5

首先第一步把16進製制轉換為2進製,它的值是111001110100101,那怎麼拆分這個2進製的值呢?因為utf8都是後6位為這個字元的unicode的碼,所以我們從右往左數6位給一一對應上,不足的位補0就好了。

這樣就得出了「玥」字的utf8編碼:11100111 10001110 10100101

作為開發人員完全可以用**實現一下,這裡用node.js真實的實現一下轉碼:

function transfertoutf8(unicode) 

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的第二種編碼格式對應上了:110***xx 10******,這樣再次開啟記事本的時候windows掃瞄檔案內容,它就會認為這是utf-8編碼的檔案,而不是gb2312!此時此刻按照utf-8來解析檔案內容當然出現了亂碼。

這時可以重新另存為檔案,把檔案格式改為gb2312來儲存,現次開啟「聯通」終於顯示了。

這個例子很極端,可以說「聯通」二字的編碼正好是個巧合,但是搞明白了編碼的細節,更有助於我們在開發中遇到問題可以快速理解其實質,並加以解決,在此記下筆記,與大家共同學習提高。

聊聊字元編碼那些事

計算機字元編碼的歷史 在windows作業系統下可以通過命令列模式檢視系統使用的字符集,如下圖所示 從圖中看到,活動頁 為936,代表gb2312 簡體中文 作者的個人計算機安裝的是windows10簡體中文版。microsoft visual studio整合開發環境採用的字元編碼是作業系統使用的...

乙個小故事

從前有乙個叫馬里的小女孩,她四歲的時候,天使飛到她家來看她,天使問她,馬里,你長大了,有什麼願望要我幫你實現嗎?馬里說,我想在我20歲生日的時候找到乙個男朋友,他叫汗斯,他要有長長的頭髮,他要會彈結他,會唱歌,我們會生4個孩子,都是女孩,她們都要去學校學跳芭蕾。馬里長大了,在她17歲的時候,她真的遇...

從乙個寓言故事看現代人

有位客人到某人家裡做客,看見主人家裡的灶上煙囪是直的,旁邊又有很多木材,於是,客人忠告主人說 煙囪要改曲,木材也要移到別的地方去,否則將來可能會有火災。主人聽了沒有做任何表示。不久,主人家裡果然失火,四周的鄰居趕緊跑過來救火,最後火被撲滅了。於是,主人烹羊宰牛,宴請四鄰,以酬謝他們救火的功勞,但是並...