本文由阿里技術團隊詹向陽(驍飏)分享,原題“一文讀懂字符編碼”,有修訂和改動。
一、引言
說起計算機字符編碼,讓我想起了科幻巨作《三體-黑暗深林》人類遇到外星文明魔戒的畫面(以下內容摘自大劉的原文)。
人類第一次近距離看到四維物體魔戒,卓文用中頻電波發送了一個問候語。這是一幅簡單的點陣圖,圖中由六行不同數量的點組成了一個質數數列:1,3,5,7,11,13。
他們沒有指望得到應答,但應答立刻出現了
.....
太空艇收到了來自“魔戒”的一系列點陣圖,第一幅是很整齊的一個8×8點陣,共六十四個點;第二幅圖中點陣的一角少了一個點,剩下六十三個;第三幅圖中又少一點,剩六十二個……“這是倒計數,也相當于一個進度條,可能表示‘它’已經收到了羅塞塔,正在譯解,讓我們等侍。”韋斯特說。
“可為什么是六十四點呢?”
“使用二進制時一個不大不小的數唄,與十進制的一百差不多。”
卓文和關一帆都很慶幸能帶韋斯特來,在與未知的智慧體建立交流方面、心理學家確實很有才能。
在倒計數達到五十七時,令人激動的事情出現了:下一個計數沒有用點陣表示,“魔戒”發來的圖片上赫然顯示出人類的阿拉伯數字56!
.....
在人類探索外星文明、邁向星辰大海的宇宙征程里,也離不開這種最最基礎的編碼問題。
前一陣跟同事碰到了字符亂碼的問題,了解后發現這個問題存在兩年了,我們程序員每天都在跟編碼打交道,但大家對字符編碼都是一知半解:“天天吃豬肉卻很少見過豬跑”,今天我就把它徹底講透!

* 推薦閱讀:如果本文太“硬”,就看看這兩篇吧:《史上最通俗,徹底搞懂字符亂碼問題的本質》、《字符編碼那點事:快速理解ASCII、Unicode、GBK和UTF-8》。
技術交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-4210-1-1.html)
二、什么是字符編碼?
我們知道計算機的世界只有0和1,如果沒有字符編碼,我們看到的就是一串"110010100101100111001....",我們的溝通就好像是在對牛彈琴,我看不懂它,它看不懂我。
字符編碼就好比人類和機器之間的翻譯程序,把我們熟知的字符文字翻譯成機器能讀懂的二進制,同時把二進制翻譯成我們能看懂的字符。
以下是百科對字符編碼的解釋:
字符編碼(Character encoding)也稱字集碼,是把字符集中的字符,編碼為指定集合中的某一對象(例如:比特模式、自然數序列、8位組或者電脈沖),以便文本在計算機中存儲或者通信網絡的傳遞。常見的例子是將拉丁字母表編碼成摩斯電碼和ASCII,比如ASCII編碼是將字母、數字和其它符號進行編號,并用7比特的二進制來表示這個整數。
字符集(Character set)是多個字符的集合,字符集種類較多,每個字符集包含的字符個數不同,常見字符集名稱:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。計算機要準確的處理各種字符集文字,就需要進行字符編碼,以便計算機能夠識別和存儲各種文字。
三、為什么計算機需要編碼?
3.1、概述
編碼(Encode)是信息從一種形式轉換為另一種形式的過程,比如用預先規定的方法將字符(文字、數字、符號等)、圖像、聲音或其它對象轉換成規定的電脈沖信號或二進制數字。
我們現在看到的一幅幅圖畫,聽到的一首首音樂,甚至我們寫的一行行代碼,敲下的一個個字符,所看到的所聽到的都是那么的真實,但其實在背后都是一串“01”的數字。你昨天在手機上看到的那個心動女孩,真實世界中并不存在,只是計算機用“01”數字幫你生成的“骷髏”而已(宅男夢碎。。。)。

3.2、二進制其實不存在
你可能認為計算機中的數據就是“01”二進制,但是實際上計算機中并沒有二進制,即便我們知道所有的內容都是存儲在硬盤中,但是你把它拆開可找不到里面有任何“0101”的數字,里面也只有盤片、磁道。就算我們放大了去看盤片,也只有凹凸不平的盤面,凸起的地方是被磁化過的,凹進去的地方是沒有被磁化的;只是我們給凸起的地方取了個名字叫數字“1”,凹進的地方取名叫數字“0”。
同樣內存里你也找不到二進制數字:內存放大了看就是一堆電容組,內存單元存儲的是“0”還是“1”取決于電容是否有電荷,有電荷我們認為他是“1”,無電荷認為他是“0”。但是電容是會放電的,時間一長,代表“1”的電容會放電,代表“0”的電容會吸電,這也是我們內存不能斷電的原因,需要定期對電容進行充電,保證“1”的電容電量有電。
再說顯示器:這個大家感受是最直接的,你透過顯示器看到的美女畫皮、日月山川,其實就是一個個不同顏色的發光二極管發出強弱不一的光點,顯示器就是一群發光二極管組成的矩陣,其中每一個二極管可以被稱為一個像素,“1”表示亮,“0”表示滅,而我們平時能看到五彩的顏色,是把三種顏色(紅綠藍三原色)的發光二極管做到了一起。那對于一個ASCII編碼“65”最后又怎么顯示成“A”的呢?這就是顯卡的功勞,顯卡中存儲了每一個字符的圖形數據(也稱字形碼),將二維矩陣的圖形數據傳給顯示器成像(如下圖所示)。

因此:所謂的0和1都是電流脈沖信號,二進制其實是我們抽象出來的數學邏輯概念。那我們為什么要用二進制表示?
因為:二進制只有兩種狀態,使用有兩個穩定狀態的物理器件就可以表示二進制中的每一位,例如用高低電平或電荷的正負性、燈的亮和滅都可以很方便地用“0”和“1”來表示,這為計算機實現邏輯運算和邏輯判斷提供了便利條件。
四、計算機編碼轉換過程
4.1、概述
正因為計算機只能表示“01”的邏輯概念,無法直接表示圖片以及文字,所以我們需要一定的轉換過程。
這其實就是我們按照一定的規則維護了字符-數字的映射關系,比如我們把“A”抽象成計算機中的“1”,當我們看到1的時候就認為這是“A”,本質上就是一張映射表,理論上你可以隨意給每個字符分配一個獨一無二的編號(character code,字符編碼)。
比如下表這樣:

接下來我們來看下一個文字從“輸入-轉碼存儲-輸出(顯示/打?。?#8221;的簡單流程。
首先:我們知道計算機是美國人發明的,規則是美國人定的,鍵盤上的按鍵也都是英文字母,所以編號不是你想怎么分配就怎么分配。對于英文字母的輸入,鍵盤和ASCII碼之間是直接對應的,鍵盤按鍵“A”對應的編號“65”,存儲到磁盤上也是“65”的二進制直譯“01000001”,這很好理解。
但是:對于漢字輸入就不是這么回事了,鍵盤上可沒有漢字對應的輸入按鍵,我們不可能直接敲出漢字字符。于是就有了輸入碼、機內碼、字形碼的轉換關系,輸入碼幫助我們把英文鍵盤按鍵轉換成漢字字符,機內碼幫助我們把漢字字符轉換成二進制序列,字形碼幫助我們把二進制序列輸出到顯示器成像。

4.2、輸入碼
我們模擬下漢字的輸入過程。
首先:打開txt文本敲下“nihao”的拼音字母,然后輸入欄會彈出多個符合條件的漢字詞組,最后我們會選擇相應的編號,就能實現漢字的輸入。
那這過程又是如何實現的呢?
計算機領域有一句如同摩西十誡般的神圣哲言:“計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決”。
這里我們再加一層按鍵字母組合和漢字的映射表,好比英漢字典,這層我們稱為輸入碼,輸入碼到內碼的過程就是一次查表轉換操作,比如“nihao”這幾個ASCII字符,大家可以隨便修改映射表以及候選編號,我可以把他映射成“你好驍飏”(如下圖所示)。

4.3、機內碼
機內碼也稱內碼,是字符編碼最核心的部分。
機內碼是字符集在計算機中實際存儲、交換、通信使用的二進制編碼,通過內碼我們可以達到高效率的存儲、傳輸文本的目的。我們的外碼(輸入碼)實現了鍵盤按鍵和字符的映射轉換,但是機內碼是讓字符真正變成了機器能讀懂的二進制語言。
4.4、字形碼
計算機中的字符都是以內碼的二進制形式表示,我們怎么把數字對應的字符在顯示器上顯示出來呢,比如數字“1”代表漢字“你”,怎么把“1”顯示成“你”?
這就需要依賴字形碼,字形碼本質上是一個n*n 的像素點陣,把某些位置的像素設置為白色(用 1 表示),其它位置像素設置為黑色(用 0 表示),每一個字符的字形都是預先存放在計算機內,而這樣的字形信息庫我們稱為字庫。
比如中文“你”的點陣圖,這樣一個 16*16 的像素矩陣,需要 16 * 16 / 8 = 32 字節的空間來表示,右邊的字模信息稱為字形碼。不同的字庫(如宋體、黑體)對同一個字符的字形編碼是不同的。

所以字符編碼到顯示的字形碼,其實又是另一張查找表,也就是字符編碼-字形碼的映射關系表。
其實我們也可以認為字符編碼是字形碼的一種壓縮方式,一個占32字節的像素點陣壓縮成了2字節的機內碼。

五、字符編碼的歷史
5.1、電報編碼
從廣義上來說,編碼的歷史很悠久,一直可以追溯到結繩記事的遠古時期,但跟現代字符編碼比較接近的還是摩爾斯電碼的發明,自此開啟了信息通信時代的大門。
摩爾斯電碼是由美國人摩爾斯在1837年發明的,比起ASCII還要早100多年,在早期的無線電上作用非常大,它是每個無線電通訊者需必知的,它的是由點dot “.” 和劃dash “-” 這兩種符號所組成的,電報中表達為短滴和長嗒,跟二進制一樣也是二元碼。
一個二元肯定不夠表示我們的字母,那么就用多個二元來表示,比如嘀嗒“.-”代表字母“A”,嗒嘀嘀嘀“-...”代表字母“B”。
摩爾斯電碼表:

5.2、編碼紀元
計算機一開始發明出來時是用來解決數學計算問題的,后來人們發現,計算機還可以做更多的事,例如文本處理等。那個時候的機器都很大,機器之間都是隔離的,沒考慮過機器的通信問題,各大廠商也各干各個的,搞自己的硬件搞自己的軟件,想怎么編碼就怎么編碼。
后來機器間需要相互通信的時候,發現在不同計算機上顯示出來的字符不一樣,在IBM上“00010100”數字代表“A”,跑到微軟系統上顯示成了“B”,大家就傻眼了。于是美國的標準化組織就跑出來制定了ASCII編碼(American Standard Code for Information Interchange),統一了游戲規則,規定了常用符號用哪些二進制數來表示。
5.3、百花齊放

統一ASCII 碼標準對于英語國家很開心,但是ASCII編碼只考慮了英文字母,后來計算機傳到歐洲地區,法國人需要加個字母符號(如:é),德國人又需要加幾個字母(Ä ä、Ö ö、ü ü、ß),幸好ASCII只用了前127個編號,于是歐洲人就將ASCII沒用完的編碼(128-255)為自己特有的符號編碼,也能很好的一起玩耍。
但是等傳到我們中國后,做為博大精深的漢語言就徹底蒙圈了,我們有幾萬個漢字,255個編號完全不夠用啊,所以有了后來的多字節編碼… 因此,各個國家都推出了本國語言的編碼表,也就有了后來的 ISO 8859 系列、GB系列(GB2312、GBK、GB18030、GB13000)、Big5、EUC-KR、JIS … ,不過為了能在計算機系統中通用,這些擴展的編碼均直接或間接兼容 ASCII 碼。
而微軟/IBM這些國際化產商為了把自己的產品賣到全世界,就需要支持各個國家的語言,要在不同的地方采用當地的編碼方式,于是他們就把全世界的編碼方式都集中到一起并編上號,并且起了個名字叫代碼頁(Codepage,又稱內碼表),所以我們有時候也會看到xx代碼頁來指代某種字符編碼,比如在微軟系統里 中文GBK編碼對應的是936代碼頁,繁體中文 Big5編碼對應的是950代碼頁。
這些既兼容ASCII又互相之間不兼容的字符編碼,后來又統稱為ANSI編碼??吹较旅孢@張圖估計大家就很熟悉了,window下面我們基本上都用ANSI編碼保存。

ANSI的字面意思并非指字符編碼,而是美國的一個非營利組織,是美國國家標準學會(American National Standards Institute)的縮寫,ANSI這個組織為字符編碼做了很多標準制定工作,后來大家習慣把這類混亂的多字節編碼叫ANSI編碼或者標準代碼頁。
ANSI編碼只是一個范稱,一般代表系統默認的編碼方式,而且并不是確定的某一種編碼方式——比如在Window操作系統里,中國區ANSI編碼指的是GB編碼,在香港地區ANSI編碼指的是Big5編碼,在韓國ANSI編碼指的是EUC-KR編碼。
5.4、天下一統
由于各個國家各搞各的字符編碼,如果有些人想裝逼中文里飚兩句韓文怎么辦呢?不好意思,你的逼級太高,沒法支持,你選擇了GB2312就只能打出中文字符。同時各大國際廠商在兼容各種字符編碼問題上也深受折磨,于是忍無可忍之下,決定開發一套能容納全世界所有字符的編碼,就有了后面大名鼎鼎的Unicode。
Unicode也叫萬國碼,包括字符集、編碼方案等。
Unicode是為了解決傳統的字符編碼方案的局限而產生的,它為每種語言中的每個字符設定了統一并且唯一的二進制編碼,在這種語言環境下,不會再有語言的編碼沖突,在同屏下也可以顯示任何國家語言的內容,這就是Unicode的最大好處。
在Unicode編碼方案里常見的有四種編碼實現方案UTF-7、 UTF-8、UTF-16、UTF-32,最為知名的就是 UTF-8。Unicode設計之初是采用雙字節定長編碼的UTF-16,但是發現歷史包袱太重推不動,最后出了個變長的UTF-8才被廣泛接受。
六、字符編碼模型
6.1、傳統編碼模型

在傳統字符編碼模型中,基本上都是將字符集里的字符用十進制進行逐一的編號,然后把十進制編號直接轉成對應的二進制碼,可以說該字符編號就是字符的編碼。
計算機在處理字符與數字的轉換關系上其實就是查找映射表的過程。
像ASCII編碼就是給每個英文字符編一個獨一無二的數字,整個編碼處理過程相對還是比較簡單的,計算機內部直接就映射成了二進制,十進制的編號只是方便我們看的。

6.2、現代編碼模型

Unicode編碼模型采用了一個全新的編碼思路,將編碼模型劃分為4 個層次(也有說5個層次的),不過第五層是傳輸層的編碼適配,放在編碼模型里嚴格來說不是很恰當。
這 4 個層次分別是:
- 1)第一層,抽象字符集 ACR(Abstract Character Repertoire):定義抽象字符集合,明確各個抽象字符;
- 2)第二層,編號字符集 CCS(Coded Character Set):將抽象字符集進行數字編號;
- 3)第三層,字符編碼方式 CEF(Character Encoding Form):將字符編號編碼為邏輯上的碼元序列;
- 4)第四層,字符編碼方案 CES(Character Encoding Scheme):將邏輯上的碼元序列編碼為物理字節序列。
下面將分別來詳細講講各層。
6.3、第一層:抽象字符集 ACR
所謂抽象字符集,就是抽象字符的合集。
它是一個無序集合,這里強調了字符是抽象的,也就是不僅包括我們視覺上能看到的狹義字符,比如“a”這樣的有形字符,也包括一些我們看不到的無形字符,比如一些控制字符“DELETE”、“NULL”等。
抽象的另一層含義是有些字形是由多個字符組合成的,比如西班牙語的 “ñ” 由“n”和“~”兩個字符組成,這一點上 Unicode 和傳統編碼標準不同,傳統編碼標準多是將 ñ 視作一個獨立的字符,而 Unicode 中將其視為兩個字符的組合。
同時一個字符也可能會有多種視覺上的字形表示,比如一個漢字有楷、行、草、隸等多種形體,這些都視為同一個抽象字符(即字符集編碼是對字符而非字形編碼),如何顯示是字形庫的事。
漢字“人”的不同形態:

抽象字符集有開放與封閉之分:開放的字符集指還會不斷新增字符的字符集,封閉字符集是指不會新增字符的字符集。比如ASCII就是封閉式的,只有128個字符,以后也不會再加,但是Unicode是開放式的,會不斷往里加新字符的,已經從最初的 7163 個增加到現在的144,697 個字符。
6.4、第二層:編號字符集 CCS
編號字符集就是對抽象字符集里的每個字符進行編號,映射到一個非負整數的集合。
編號一般用方便人類閱讀的十進制、十六進制來表示,比如“A”字符編號“65”,“B”字符編號是“66”。
大家需要清楚對于有些字符編碼的編號就是存儲的二進制序列,如ASCII編碼;有些字符編碼的編號跟存儲的二進制序列并不一樣,比如GB2312、Unicode等。
另外:編號字符集合是有范圍限制的,比如ASCII字符集范圍是0~127,ISO-8859-1范圍是0~256,而GB2312是用一個94*94的二維矩陣空間來表示,Unicode是用Plane平面空間的概念來表示,這稱為字符集的編號空間。
編號空間中的一個位置稱為碼點( Code Point 代碼點 )。一個字符占用的碼點所在的坐標(非負整數值對)或所代表的非負整數值,就是該字符的碼值(碼點編號)。
ASCII碼點編號:

6.5、第三層:字符編碼方式 CEF
抽象字符集和編號字符集是站在方便我們理解的角度來看的,所以最后我們需要翻譯成計算機能懂的語言,將十進制的編號轉換成二進制的形式。
因此:字符編碼方式就是將字符集的碼點編號,轉換成二進制碼元序列( Code Unit Sequence )的過程。
碼元:字符編碼的最小處理單元,比如ASCII一個字符等于一個字節,屬于單字節碼元;UTF-16一個字符等于兩個字節,處理過程是按字“word”來處理,所以是雙字節碼元;UTF-8是多字節編碼,有單字節字符,也有多字節字符,每次處理是按單個單個字節解析處理,所以處理最小單位是字節,也屬于單字節碼元。
這里大家可能會有疑問:十進制直接轉二進制不就好了嗎,為什么要單獨抽出這么一層?
早期的字符編碼確實也是這么處理的,十進制和二進制之間是直接轉換過去的,比如ASCII碼,字符“A”的十進制是“65”,那對應的二進制就是“1000001”,同時存儲到硬盤里的也是這個二進制,所以那時候的編碼比較簡單。
隨著后來多字節字符編碼(Muilti-Bytes Character Set,MBCS多字節字符集)的出現,字符編號和二進制之間不是直接轉換過去的,比如GB2312編碼,“萬”字的區位編號是“45,82”,對應的二進制機內碼卻是“1100 1101 1111 0010”(其十進制是“205,242”)。
如果這里不轉換直接映射成二進制碼會出什么問題呢?“萬”字的字符編號“45,82”,45在ASCII里是“-”,82是“U”,那到底是顯示兩個字符“-U”還是顯示一個字符“萬”字,為了避免這種沖突 所以增加了前綴處理,詳細的過程會在下文具體來講解。
6.6、第四層:字符編碼方案 CES
字符編碼方案也稱作“序列化格式“( Serialization Format ),指的是將字符編號進行編碼之后的碼元序列映射為字節序列(即字節流)的形式,以便經過編碼后的字符能在計算機中進行處理、存儲和傳輸。
字符編碼方式CEF有點像我們數據庫結構設計里的邏輯設計,而這一層編碼方案CES就像是物理設計了,將碼元序列映射為跟特定的計算機系統平臺相關的物理意義上的二進制過程。
這里大家可能又會有疑問:為什么二進制的碼元序列和實際存儲的二進制又會不一樣呢?
這主要是計算機的大小端序造成的,具體端序內容會在UTF-16編碼部分詳細介紹。
“大小端序名詞”出自Jonathan Swift的《格列夫游記》一書 :
所有人都認為,吃雞蛋前,原始的方法是打破雞蛋較大的一端??墒钱斀窕实鄣淖娓感r候吃雞蛋,一次按古法打雞蛋時碰巧將一個手指弄破了,因此他的父親,當時的皇帝,就下了一道敕令,命令全體臣民吃雞蛋時打破雞蛋較小的一端,違令者重罰。
老百姓們對這項命令極為反感。歷史告訴我們,由此曾發生過六次叛亂,其中一個皇帝送了命,另一個丟了王位…關于這一爭端,曾出版過幾百本大部著作,不過大端派的書一直是受禁的,法律也規定該派的任何人不得做官。

▲ 圖片引用自《面試必考,史上最通俗大小端字節序詳解》
對大小端字節序問題感興趣的可以詳讀:《腦殘式網絡編程入門(九):面試必考,史上最通俗大小端字節序詳解》一文。
七、常見字符編碼1:ASCII
很久以前:計算機制造商都是按各自的方式來將字符渲染到屏幕上,當時的計算機動不動可就是一套房子的大小,這家伙可不是誰都能玩的起的,那時人們并不關心計算機如何交流。
隨著上世紀七八十年代微處理器的出現,計算機變得越來越小,個人計算機開始進入大眾的視線,隨后出現了井噴式的發展,但是之前廠商都是各自為政,沒考慮過自家的產品要兼容別人家的東西,導致在不同計算機體系間的數據轉換變得十分蛋疼,因此美國的標準協會在1967年制定出了ASCII編碼,到目前為止共定義了128個字符。
ASCII 編碼:

(注意:該表是列表示字節高 4 位。上圖引用自《字符編碼那點事:快速理解ASCII、Unicode、GBK和UTF-8》)
其中:前 32 個(0~31)是不可見的控制字符,32~126 是可見字符,127 是 DELETE 命令(鍵盤上的 DEL 鍵)。
其實:早在ASCII之前,IBM在1963年也推出過一套字符編碼系統EBCDIC,跟ASCII碼一樣囊括了控制字符、數字、常用標點、大小寫英文字母。
EBCDIC 編碼:

但是:他的字符編號并不是連續的,這給后續程序處理帶來了麻煩,后來ASCII 編碼吸取了 EBCDIC 的經驗教訓,給英文單詞分配了連續的編碼,方便程序處理,因此被后來廣泛接受。
ASCII 和 EBCDIC 編碼相比:除了字符連續排列之外,最大的優點是ASCII 只用了一個字節的低 7 位,最高位永遠是 0??蓜e小看了這個最高位的 0,看似無足輕重,但這是ASCII設計最成功的地方,后面介紹各編碼原理的時候你會發現,正是因為這個高位0,其它編碼規范才能對 ASCII 碼無縫兼容,使得 ASCII 被廣泛接受。
八、常見字符編碼2:ISO-8859系列
美國市場雖然統一了字符編碼,但是計算機制造商在進入歐洲市場的時候又遇到了麻煩。。。
歐洲的主流語言雖然也是用拉丁字母,但卻存在很多擴展體,比如法語的“é”,挪威語中的“Å”,都無法用 ASCII 表示。但是大家發現ASCII后面的128個還沒有被使用可以利用起來,這對于歐洲主流語言就足夠了。
于是就有了大家所熟知的這個ISO-8859-1(Latin-1),它只是擴展了ASCII后128個字符,還是屬于單字節編碼。同時為了兼容原先的 ASCII碼,當最高位是0的時候仍然表示原先的 ASCII 字符不變,當最高位是1的時候表示擴展的歐洲字符。

但是到這里還沒有完:剛說了這只是歐洲主流的語言,但主流語言里沒有法語使用的 œ、Œ、Ÿ 三個字母,也沒有芬蘭語使用的 Š、š、?、? ,而單字節編碼里的256個碼點都被用完了,于是就出現了更多的變種 ISO-8859-2/3/.../16 系列,他們都兼容 ASCII,但彼此間又不完全兼容。
ISO-8859-n系列字符集如下:
- 1)ISO8859-1 字符集,也就是 Latin-1,是西歐常用字符,包括德法兩國的字母;
- 2)ISO8859-2 字符集,也稱為 Latin-2,收集了東歐字符;
- 3)ISO8859-3 字符集,也稱為 Latin-3,收集了南歐字符;
- 4)ISO8859-4 字符集,也稱為 Latin-4,收集了北歐字符;
- 5)ISO8859-5 字符集,也稱為 Cyrillic,收集了斯拉夫語系字符;
- 6)ISO8859-6 字符集,也稱為 Arabic,收集了阿拉伯語系字符;
- 7)ISO8859-7 字符集,也稱為 Greek,收集了希臘字符;
- .......
九、常見字符編碼3:GB系列
9.1、概述
當計算機進入東亞國家的時候,廠商們更傻眼了,美國和歐洲國家語言基本都是表音字符,一個字節就足夠用了,但亞洲國家有不少是表意字符,字符個數動輒幾萬十幾萬的,一個字節完全不夠用。
所以我們國家有關部門按照ISO規范設計了GB2312雙字節編碼。但是GB2312是一個封閉字符集,只收錄了常用字符總共也就7000多個字符,因此為了擴充更多的字符包括一些生僻字,才有了之后的GBK、GB18030、GB13000(“GB” 為 “國標” 的漢語拼音首字母縮寫)。
按照 GB 系列編碼方案,在一段文本中,如果一個字節是 0~127,那么這個字節的含義與 ASCII 編碼相同,否則,這個字節和下一個字節共同組成漢字(或是 GB 編碼定義的其他字符),所以GB系列都是兼容ASCII編碼的。

9.2、GB2312
GB2312是使用兩個字節來表示漢字的編碼標準,共收入漢字6763個和非漢字圖形字符682個。
為了避免與 ASCII 字符編碼(0~127)相沖突,規定表示一個漢字的編碼字節其值必須大于127(即字節的最高位為 1 ),并且必須是兩個大于 127 的字節連在一起來共同表示一個漢字( GB2312 為雙字節編碼),所以GB2312 屬于變長編碼,當是英文字符的時候占一個字節,中文字符的時候占兩個字節,可以認為 GB2312是對 ASCII 的中文擴展。
GB2312字符集編號空間是一個94*94的二維表,行表示區(高位字節),列表示位(低位字節),每區有94個位,每個區位對應一個字符,稱為區位碼。區位碼上加2020H,就得到國標碼,國標碼上加8080H,就得到常用的計算機機內碼。
這里引入了區位碼、國標碼、機內碼概念,下面我們說下三者的關系。
9.2.1國標碼
國標碼是我國漢字信息交換的標準編碼,規定由4位16進制數組成,用兩個低7位字節表示,為了避開 ASCII 字符中的前32個控制指令字符,所以每個字節都是從第33個編號開始。
如下圖所示:

9.2.2區位碼
由于上述國標碼的16進制可編碼區不夠直觀不方便我們使用,所以我們把他映射成了十進制的94*94二維表編號空間,我們稱之為區位碼,同時區位碼也可以當成一種外碼使用,輸入法可以直接切換成區位碼進行漢字輸入。
不過這種輸入法無規則可言 人們很難記住區位編號,用的人也不多了。
下圖是區位碼的二維表,比如“萬”字是45 區 82 位,所以“萬” 字的區位碼是“45,82”。

其中:
- 1)01~09區(682個):特殊符號、數字、英文字符、制表符等(包括拉丁字母、希臘字母、日文平假名及片假名字母、俄語西里爾字母等在內的682個全角字符);
- 2)10~15區:空區,留待擴展;
- 3)16~55區(3755個):常用漢字(也稱一級漢字),按拼音排序;
- 4)56~87區(3008個):非常用漢字(也稱二級漢字),按部首/筆畫排序;
- 5)88~94區:空區,留待擴展。
9.2.3機內碼
GB2312國標碼規范是覆蓋掉ASCII中可見部分的符號和英文字母,使用兩個7位碼將其中的英文字母和符號重新編入。
但是這樣產生一個弊端:早期用ASCII碼編碼的英文文章無法打開,一打開就是亂碼,也就是說應該要兼容早期ASCII碼而不是覆蓋它。
后來微軟為了解決這個問題:將字節的最高位設為1,因為ASCII中使用7位,最高位為0,轉換后的編碼稱為機內碼(內碼),這種方式本質上是修改了GB2312的編碼標準,最后被大家接受沿用。
總結下三者轉換關系:區位碼 ---> 區碼和位碼分別 + 32(即 + 20H )得到國標碼 ---> 再分別 + 128(即 + 80H)得到機內碼(與 ACSII 碼不再沖突)。

9.3、GBK
GBK即“國標擴展”的意思,因為GB2312雙字節的最高位都要求大于1,上限也不會超過1萬個字符,所以對此進行了擴展,對GB2312的字符不重新編碼直接沿用,因此完全兼容GB2312。
GBK雖然也是雙字節編碼,但是只要求第一個字節大于 127 就固定表示這是一個漢字的開始,正因為如此,GBK的編碼空間比GB2312大很多。
GBK 整體編碼范圍為 8140-FEFE,首字節在 81-FE 之間,尾字節在 40-FE 之間,剔除 xx7F 一條線,總計 23940 個碼位,共收入 21886 個漢字和圖形符號。其中 GBK/1 收錄除 GB 2312 字符外的其他增補字符,GBK/2 收錄 GB2312 字符,GBK/3 收錄 CJK 字符,GBK/4 收錄 CJK 字符和增補字符,GBK/5 為非中文字符,UDC 為用戶自定義字符。
詳細如下如所示:

這里大家可能會有兩個疑問:為什么尾字節要從40開始,而不是00開始?為什么要排除 FF、xx7F這兩條線的編號?
GBK的尾字節編碼高位沒有強制要求是1,當高位是0時跟ASCII碼是沖突的,ASCII碼里00-40之間大部分都是控制字符,所以排除控制字符主要是為了防止丟失高字節導致出現系統性嚴重后果。
排除FF是為了兼容GB2312,GB2312這個位是保留不使用的;而7F表示DEL字符就是向后刪除一個字符,如果傳輸過程中丟失首字節那么就會出現嚴重的后果,所以需要將xx7F也排除,這是所有編碼方案都需要注意的地方。
9.4、GB18030
隨著計算機的發展,GBK的2萬多個字符也還是扛不住。
于是2000年我國又制定了新標準 GB18030,用來替代 GBK 標準。GB18030是強制性標準,現在在中國大陸銷售的軟件都支持 GB18030。
GB18030其實是對齊Unicode標準的,里面包括了所有Unicode字符集,也算是Unicode的一種實現(UTF)。
那既然有了UTF我們為什么還要搞一套Unicode實現?
主要是UTF-8/UCS-2他們是不兼容GB2312的,如果直接升級那么就全亂碼了,所以GB18030是為了兼容GB系列,是GBK、GB2312的超集,當我們原先的GB2312(GBK)軟件考慮升級到國際化Unicode時,可以直接使用GB18030進行升級。
GB18030雖然也是GB2312的擴展,但它和GBK的擴展方式不一樣,GBK主要是充分利用了GB2312的一些沒定義的編碼空間,而GB18030采用的是字節變長編碼,單字節區兼容ASCII、雙字節區兼容GBK、四字節區對齊所有Unicode 碼位。
實現原理上主要是采用第二字節未使用到的0x30~0x39編碼空間來判斷是否四字節。

具體就是:
- 1)單字節,其值從0到0x7F。
- 2) 雙字節,第一個字節的值從0x81到0xFE,第二個字節的值從0x40到0xFE(不包括0x7F)。
- 3) 四字節,第一個字節的值從0x81到0xFE,第二個字節的值從0x30到0x39,第三個字節的值從0x81到0xFE,第四個字節的值從0x30到0x39。
十、常見字符編碼4:UNICODE
10.1、背景介紹
在統一碼之前,各國創造了大量的節編碼標準,有單字節的、雙字節的(如 GB 2312、Shift JIS、Big5 、ISO8859等),各自又相互不兼容。在1987 年,蘋果、Sun、微軟等公司開始討論囊括全世界所有字符的統一編碼標準,組成了 Unicode 聯盟,這個期間做了很多研討工作,討論核心要點如下。
1)目前世界上有多少個字符,需要幾個字節存儲?
工作組統計了當時全世界的報紙等刊物,結論是兩個字節足以囊括全世界有實用意義的字符(當然這只統計了當前使用的字符,不包括古代語言或者廢棄語言)。
2)采用固定長度編碼還是變長編碼?
一種采用變長編碼形式,對于 ASCII 字符使用一個字節,其他字符使用兩個字節,類似 GBK。另一種采用定長編碼形式,不管是不是 ASCII 字符統一使用兩個字節。
方案選擇上主要從計算機處理過程中的時間和空間兩個維度,也就是編解碼的執行效率和存儲大小兩方面。
最后結論是采用雙字節定長編碼,因為定長帶來的空間變大在整體傳輸、存儲成本上其實影響并不大,而定長編碼處理效率會明顯高于變長編碼,所以早期 Unicode 采用了定長編碼形式。
3)中、日、韓中有很多相近的表意文字是否可以統一?
由于漢字表意文字字符量較大,如果可以統一那么能大幅減少收錄漢字的數量。
所以最初收錄漢字遵循兩個基本原則:表意文字認同原則和字源分離原則。
所謂表意文字認同原則:即“只對字,不對形”編碼,將同一字的不同字形(即異體字)合并。例如“房”字的第一筆,在中日韓的寫法都不同,但它本身是同一個字,只給一個編碼,而寫法的不同交由字體進行區分。
字源分離原則:是指一個字源中同時收錄了同一個字的不同字形,則給予兩個字形分別編碼。例如:之前GBK中就收錄了“戶”、“戶”、“戸”三個字,那么Unicode也需要保留三個字,如果直接合并會造成使用上的困擾。
例如下面這句話如果不做字源分離,會是什么情況呢?
原句 :戶有三種寫法,分別是“戶”、“戶”、“戸”,
改寫后:戶有三種寫法,分別是“戶”、“戶”、“戶”
10.2、Unicode介紹
Unicode 稱為統一碼(也叫萬國碼),是按現代編碼模型進行設計的一套字符編碼體系,涵蓋抽象字符集、編號、邏輯編碼、編碼實現。
Unicode是為了解決傳統的字符編碼方案的局限而產生的,在這種語言環境下,不會再有語言的編碼沖突,可以在同屏下顯示任何國家的語言。
UTF-n編碼(Unicode Transformation Format Unicode字符集轉換格式,n表示碼元位數)是Unicode這套編碼體系里的編碼實現CES部分,像UTF-8、UTF-16、UTF-32都是將數字轉換到實際的二進制編碼實現,Unicode的編碼實現除了UTF系列之外,還有UCS-2/4,GB18030等。但是現在很多人誤把Unicode當成只是一個字符編號,這其實是不對的。
Unicode可以容納世界上所有國家的文字和符號,其編號范圍是0-0x10FFFF,有1,114,112個碼位,為了方便管理劃分成17個平面,現已定義的碼位有238,605個,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面0又稱為基本多語言平面(Basic Multilingual Plane,簡稱BMP),這個平面基本涵蓋了當今世界上正在使用中的常用字符。我們平常用到的字符,一般都是位于 BMP 平面上的,其范圍擁有 65,536 個碼點,其他平面統稱增補平面,關于平面的概念會在UTF-16章節詳細介紹。
10.3、與UCS的關系

說起Unicode我們不得不提UCS(全稱Universal Multiple-Octet Coded Character Set 通用多八位編碼字符集),國際標準編號ISO/IEC 10646,是由 ISO 和 IEC 兩家國際標準組織聯合成立的工作組設計的一套新的統一字符集項目,目的與Unicode 聯盟一樣致力于開發一款全世界通用的編碼集。
早在1984 年ISO 和 IEC 兩家組織就成立了一個聯合工作組來設計一套新的統一字符集標準,但是這兩個組織都不知道對方的存在,直到Unicode聯盟1988年發布了Unicode草案(UCS草案1989年發布),才發現大家在做同一件事,沒有必要搞兩套標準 所以后面又考慮合并。
由于UCS 最初設計的是 31 位編碼空間(UCS-4編碼實現),可以容納 2^31 約 21 億個字符,而Unicode是16位空間(UTF-16編碼實現),所以最開始Unicode 打算作為 UCS 的真子集,即 Unicode 中的每個字符都存在于 UCS 中,而且兩者的碼點相同,但 UCS 中的字符(編號超過65,536的)則不一定存在于 Unicode 中。
不過:由于雙方利益關系并沒有說誰解散誰,最后雙方作出一些妥協保持一致共同發展,兩個標準中相同字符的編碼(碼點)必須是一樣的。這是一個屁股決定腦袋的決策,如果最初Unicode知道UCS的存在,就不會再出現Unicode了。
當然合并工作不是一蹴而就的而是經過多輪迭代, ISO/IEC 和 Unicode在 1993 年發布了第一版相互兼容版本,到了 1996年Unicode 2.0標準發布時,Unicode 字符集和 UCS 字符集(即 ISO/IEC 10646-1 )基本保持了一致,同時Unicode為了跟UCS的四字節保持一致推出了UTF-32編碼實現,UCS為了跟Unicode的兩字節保持一致推出了UCS-2編碼實現。
所以:現在我們可以認為UCS和Unicode是同一個東西,比如我們常見的java內部運行就采用的是UTF-16編碼,而window操作系統采用的是UCS-2,他們都是同一個Unicode標準。
為什么這里使用的是2字節編碼,而不是4字節呢?先留個懸念,后續會詳細講解。
10.4、UTF-16(Java內部編碼)
UTF是Unicode Transfer Format的縮寫,即把Unicode轉做某種格式的意思,所以UTF-16是Unicode編碼里的其中一種實現方式,16代表的是字節位數,占兩個字節(UTF-32則表示4個字節)。
Unicode 設計之初是采用UTF-16這種雙字節定長編碼的,其字符編號就是對應的二進制編號,也就是說第二層的CCS和第三層的CEF是一致的。比如漢字“萬”的 Unicode 碼點是 “U+4E07”,其二進制序列就是直譯的“0100 1110 0000 0111 ”,這種編碼方式的優點是高效,不需要檢查標志位,但缺點是不兼容ASCII,ASCII編碼的文本都會顯示亂碼。
不過:后來Unicode聯盟發現 16 位編碼空間根本不夠用,與此同時 ISO/IEC組織也覺得 UCS的 32 位編碼空間太多了,實際中根本沒有幾十億字符,也挺浪費空間的。
所以最終 Unicode 聯盟和 ISO/IEC 工作組達成一致:兩者使用統一的編碼空間“ 0000 ~ 10FFFF”(即 UCS 保證永遠不分配大于 10FFFF 的字符碼點),而且雙方在字符編碼上保持同步,即一方標準中增加了字符,也要通知另一方同步。
于是:Unicode在UTF-16基礎上拓展編碼空間到 21 位,UCS則搞了一個雙字節的UCS-2編碼實現。
UTF-16 編碼是雙字節的,上限也只有6w多個碼點,怎么讓他支持到10FFFF(100w+)個碼點呢?
本質就是:多加幾個字節來表示更多的字符,只是UTF-16不像UCS那樣采用定長4字節,而是使用變長的形式,但是這個跟UTF-8變長方式又不太一樣,他是采用代理對的方式實現,大部分常用字符用一個碼元表示(定長2個字節),其他擴展的特殊字符用兩個碼元表示(定長4字節)。
10.4.1代理對
UTF-16跟UTF-8、GB系列等都算是變長字節,但是設計初衷卻不一樣,像GBK是為了兼容ASCII,但是UTF-16一開始就沒考慮要兼容ASCII,所以他的變長是為了節約存儲空間而采用的自然增長方案,當空間不夠的時候增長到4個字節。
那問題來了,我怎么知道存儲的4個字節是表示一個字符,還是兩個字符呢?比如當程序遇到字節序列01001110 00101101 01010110 11111101時,到底是判斷成一個字符還是兩個字符?
這就需要一個前導識別,比如GB2312識別第一個字節高位是不是1來判斷是單字節還是雙字節,但是UTF-16的高位1已經被用來編碼了,當然這也難不倒我們,第一位被用了那么就用前幾位的組合形式。
UTF-16采用了代理對來解決,也就是高半區編碼(前兩個字節)范圍D800-DBFF(稱為代理碼點),低半區編碼(后兩個字節)范圍DC00-DFFF,組成一個四個字節表示的字符。

上述前導6位組合也是有講究的,ISO組織要求編號范圍是0~10FFFF,也就是說用20位就可以表示10FFFF個字符,對于雙碼元就是每個碼元各自負責10位,一個碼元是16位,數字位占去10位后,剩下的6位做為前導位。
當UTF-16使用一個碼元表示的時候,Unicode字符編號跟碼元序列是等值映射的,但是當采用雙碼元后,字符編號跟碼元序列就需要轉換了。
下面是碼元和Unicode編號值之間的計算公式。
換算碼元序列(CH高半區/CL低半區):

換算字符編號(CH高半區/CL低半區):

10.4.2平面空間
UTF-16把編碼空間0000 ~ 10FFFF切成了17個平面,其實就是劃分成17個區塊,每個平面空間碼點數都是=65536個,第一個平面稱為基本多語言平面(Basic Multilingual Plane,簡稱BMP),這個平面涵蓋了當今世界上最常用的字符,固定使用定長兩個字節,除此之外的字符都放到增補平面里,都是使用兩個碼元的定長4個字節。
下面是各個平面的用途:

增補平面的編號是采用雙碼元4個字節來表示的,去除代理對之后有效位數是20位,然后將這20位的編號再劃成16個平面區域,其中高半區的數字位里取出4位表示平面,剩下的16位表示每個平面可以表示的字符數也就是2的16次方65536個(兩個字節大?。?/p>

UTF-16可看成是UCS-2的父集。在沒有輔助平面前,UTF-16與UCS-2所指的是同一的意思。但當引入輔助平面字符后,就稱為UTF-16了。
10.4.3字節序
字節序顧名思義是指字節的順序,對于單字節編碼來說,一個字符對應一個字節,也就不存在字節序問題。但是對于UTF-16這種定長多字節編碼,就有字節順序問題了。
字節序其實跟操作系統和底層硬件有關,不僅只是UTF-16這種多字節編碼存在字節序,只要是多字節類型的數據都存在字節順序問題,比如short、int、long。
為了方便說明,我們這里舉個例子:比如存一個整數值“305419896”對應16進制是0x12345678,有人習慣從左到右按順序去存,也有人說高位當然要放到高位地址而低位放到低位地址,要從右往左存。
于是就有了下面兩種存取方式:

其實這兩種方式沒有孰優孰劣,只是我們認知習慣有所不同 最終的設計不同,說來這都是阿拉伯人的鍋啊,為什么數字高位非要在左邊,這也引起了著名的大小端之爭。
因此字節序也就有了大端和小端的概念,也形成了各自的陣營,比如Windows、FreeBSD、Linux 是小端序,Mac是大端序。其實大小端序并沒有技術上的好壞之分。
小端序(Little-Endian):就是低位字節(即小端字節、尾端字節)存放在內存的低地址,而高位字節(即大端字節、頭端字節)存放在內存的高地址。
大端序(Big-Endian ):就是高位字節(即大端字節、頭端字節)存放在內存的低地址,低位字節(即小端字節、尾端字節)存放在內存的高地址。

▲ 圖片引用自《面試必考,史上最通俗大小端字節序詳解》
對大小端字節序問題感興趣的可以詳讀:《腦殘式網絡編程入門(九):面試必考,史上最通俗大小端字節序詳解》一文。
10.5、UTF-8
10.5.1概述
Unicode還是UCS最初都是采用多字節定長編碼,由于沒有兼容現有的 ASCII 標準的文件和軟件,新標準很難被推廣,于是兼容ASCII版本的UTF-8就誕生了。
UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字符編碼,是現代字符編碼模型中的第三層 CEF 。它可以用一至四個字節對 Unicode 字符集中的所有有效編碼點進行編碼,屬于Unicode標準的一部分,UTF-8 就是為了解決向后兼容 ASCII 碼而設計,Unicode 中前 128 個字符(與 ASCII 碼一一對應),使用與 ASCII 碼相同的二進制值的單個字節進行編碼,這使得原來處理 ASCII 字符的軟件無須或只須做少部分修改,即可繼續使用。因此,它逐漸成為電子郵件、網頁及其他存儲或發送文字優先采用的編碼方式。
—— 維基百科
UTF-8需要兼容ASCII,所以也需要有前綴碼來控制,前綴規則如下:
- 1)如果首字節以 0 開頭,則是單字節編碼(即單個單字節碼元);
- 2)如果首字節以 110 開頭,則是雙字節編碼(即由兩個單字節碼元所組成的雙碼元序列);
- 3)如果首字節以 1110 開頭,則是三字節編碼(即由三個單字節碼元所組成的三碼元序列),以此類推。

理論上UTF-8變長可以超過4個字節,只是Unicode聯盟規范上限是10FFFF,所以UTF-8規則設計上也限制了大小。
10.5.2程序算法
用文字不太好描述算法結構,我們就直接來欣賞一下UTF-8鼻祖寫的這段解析代碼,這是Ken Thompson(B語言、C語言的作者、Unix之父)和 Rob Pike 用一個晚上寫出來的編解碼算法,代碼非常簡短精煉,為了方便閱讀我加了注釋解讀。
typedefstruct
{
intcmask; //前綴碼掩碼
intcval; //前綴碼
intshift; //移動位數
longlmask; //Unicode值掩碼
longlval; //Unicode下限值
} Tab;
staticTab tab[] =
{
0x80, 0x00, 0*6, 0x7F, 0, /* 1 byte sequence */
0xE0, 0xC0, 1*6, 0x7FF, 0x80, /* 2 byte sequence */
0xF0, 0xE0, 2*6, 0xFFFF, 0x800, /* 3 byte sequence */
0xF8, 0xF0, 3*6, 0x1FFFFF, 0x10000, /* 4 byte sequence */
0xFC, 0xF8, 4*6, 0x3FFFFFF, 0x200000, /* 5 byte sequence */
0xFE, 0xFC, 5*6, 0x7FFFFFFF, 0x4000000, /* 6 byte sequence */
0, /* end of table */
};
/**
* 把一個多字節序列轉換為一個寬字符
*
* @param p 存放計算后的unicode值
* @param s 需要解析的UTF-8字節序列
* @param n 字節長度
* @return 解析的字節長度
*/
intmbtowc(wchar_t*p, char*s, size_tn)
{
longl; intc0, c, nc; Tab *t;
if(s == 0) return0;
nc = 0;
//異常校驗(可不用關注)
if(n <= nc) return-1;
//c0 此處備份一下首字節,后續需要用到前綴碼
c0 = *s & 0xff;
//l 保存 Unicode 結果
l = c0;
/* 遍歷tab,從單字節結構->2字節結構->..依次檢查找到對應tab */
for(t=tab; t->cmask; t++) {
//字節數+1,字節數和tab結構是對應的,也就是當nc=1時 tab結構是單字節,nc=2是tab是兩字節
nc++;
/* 判斷前綴碼跟當前的tab是否一致, 如果一致計算最終unicode值并返回*/
if((c0 & t->cmask) == t->cval) {
//通過 & Unicode有效值掩碼,移除高位前綴碼,得到最終unicode值
l &= t->lmask;
//異常校驗
if(l < t->lval) return-1;
//保存結果并反回
*p = l;
returnnc;
}
//異常校驗
if(n <= nc) return-1;
//讀取下個字節;如果上面判斷前綴碼不一致,說明需要再讀取下個字節
s++;
//計算有效位的值,目的是去除UTF-8 編碼從第二個字節開始的高兩位10
// 例如 s=10101111、0x80=10000000 計算結果是00101111,這樣就去除了高位前綴10
c = (*s ^ 0x80) & 0xFF;
//異常校驗
if(c & 0xC0) return-1;
//重新計算unicode值,根據UTF-8規則c只有低 6 位有效,所以通過移位把c填入到l的低6位
l = (l<<6) | c;
}
//返回異常
return-1;
}
10.5.3容錯性
通過上面的程序我們知道:解析過程是一個字節一個字節往下處理的,我們在傳輸過程中如果發生局部的字節錯誤、丟失,或者中間有一個字節規則對不上,會不會影響整個文本的解析?
我們先來看下其他編碼的容錯情況:從對于單字節的ASCII碼來說,丟失一個字節就丟失一個字符,并不影響后續文本的內容,比如Hello world,丟失b2字節后內容是Hllo world少個e而已。
我們再來看GB2312這種多字節編碼:如果丟失了b2字節那么整個文本都亂套了,這是最糟糕的,大部分多字節編碼都有類似問題,一旦出現錯誤可能導致整個文件都需要重傳。

接下來我們看看UTF-8是如何避免這種“一顆老鼠屎壞了一鍋粥”的情況:UTF-8 的碼元序列的第一個字節指明了后面所跟字節的個數,比如首字節高位是0就表示單字節,110表示總共兩個字節,1110表示三個字節依次類推,除首字節之外后續字節都是10開頭。所以UTF-8的前綴碼具有很強的魯棒性,即使丟失、增加、改變個別字節也不會導致后續字符全部錯亂這樣的傳遞性、連鎖性的錯誤問題。

十一、本文總結
看起來好像誰都懂的字符編碼知識,深入了解之后發現也有這么濃重的發展歷程,試想一下,如果計算機還是跟之前大型機一樣,個人計算機沒有井噴式發展起來就沒有這些字符編碼的事了,如果ASCII當初就設計成多字節編碼,也沒有后面UNICODE什么事了。
計算機字符編碼發展歷程其實就是一個很典型的架構設計問題。
到底好的架構是設計出來的,還是演化出來的?
有人說靠演化出來的:沒有設計的產品架構是沒有靈魂的,發展的路上死的很快。
有人說靠設計出來的:這是一種完美主義者,你超前設計個50年、100年等你設計出來了,說不定公司都已經倒閉了,有很多叫好不叫做的產品、架構也比比皆是。
其實:一個好的架構是既要靠設計又要靠演化,老話說的好三分靠設計七分靠演化,我們既要學會務實,也要懂得前瞻,至少我們首先需要活下來。
十二、參考資料
[1] Unicode中文編碼表
[2] Every Developer Should Know About The Encoding
[4] 字符編碼那點事:快速理解ASCII、Unicode、GBK和UTF-8
附錄:阿里技術文章匯總
《阿里釘釘技術分享:企業級IM王者——釘釘在后端架構上的過人之處》
《阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路》
《來自阿里OpenIM:打造安全可靠即時通訊服務的技術實踐分享》
《釘釘——基于IM技術的新一代企業OA平臺的技術挑戰(視頻+PPT) [附件下載]》
《阿里技術結晶:《阿里巴巴Java開發手冊(規約)-華山版》[附件下載]》
《重磅發布:《阿里巴巴Android開發手冊(規約)》[附件下載]》
《阿里技術分享:電商IM消息平臺,在群聊、直播場景下的技術實踐》
《阿里技術分享:閑魚IM基于Flutter的移動端跨端改造實踐》
《阿里IM技術分享(三):閑魚億級IM消息系統的架構演進之路》
《阿里IM技術分享(四):閑魚億級IM消息系統的可靠投遞優化實踐》
《阿里IM技術分享(五):閑魚億級IM消息系統的及時性優化實踐》
《阿里IM技術分享(六):閑魚億級IM消息系統的離線推送到達率優化》
《阿里IM技術分享(七):閑魚IM的在線、離線聊天數據同步機制優化實踐》
《阿里IM技術分享(八):深度解密釘釘即時消息服務DTIM的技術設計》
《阿里IM技術分享(九):深度揭密RocketMQ在釘釘IM系統中的應用實踐》
(本文已同步發布于:http://www.52im.net/thread-4210-1-1.html)
作者:Jack Jiang (點擊作者姓名進入Github)
出處:http://www.52im.net/space-uid-1.html
交流:歡迎加入即時通訊開發交流群 215891622
討論:http://www.52im.net/
Jack Jiang同時是【原創Java
Swing外觀工程BeautyEye】和【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
本博文
歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。