<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

    我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
    posts - 494, comments - 13, trackbacks - 0, articles - 1

    本文由阿里技術(shù)團隊詹向陽(驍飏)分享,原題“一文讀懂字符編碼”,有修訂和改動。

    一、引言

    說起計算機字符編碼,讓我想起了科幻巨作《三體-黑暗深林》人類遇到外星文明魔戒的畫面(以下內(nèi)容摘自大劉的原文)。

    人類第一次近距離看到四維物體魔戒,卓文用中頻電波發(fā)送了一個問候語。這是一幅簡單的點陣圖,圖中由六行不同數(shù)量的點組成了一個質(zhì)數(shù)數(shù)列:1,3,5,7,11,13。

    他們沒有指望得到應(yīng)答,但應(yīng)答立刻出現(xiàn)了

    .....

    太空艇收到了來自“魔戒”的一系列點陣圖,第一幅是很整齊的一個8×8點陣,共六十四個點;第二幅圖中點陣的一角少了一個點,剩下六十三個;第三幅圖中又少一點,剩六十二個……“這是倒計數(shù),也相當(dāng)于一個進度條,可能表示‘它’已經(jīng)收到了羅塞塔,正在譯解,讓我們等侍。”韋斯特說。

    “可為什么是六十四點呢?”

    “使用二進制時一個不大不小的數(shù)唄,與十進制的一百差不多。”

    卓文和關(guān)一帆都很慶幸能帶韋斯特來,在與未知的智慧體建立交流方面、心理學(xué)家確實很有才能。

    在倒計數(shù)達到五十七時,令人激動的事情出現(xiàn)了:下一個計數(shù)沒有用點陣表示,“魔戒”發(fā)來的圖片上赫然顯示出人類的阿拉伯?dāng)?shù)字56!

    .....

    在人類探索外星文明、邁向星辰大海的宇宙征程里,也離不開這種最最基礎(chǔ)的編碼問題。

    前一陣跟同事碰到了字符亂碼的問題,了解后發(fā)現(xiàn)這個問題存在兩年了,我們程序員每天都在跟編碼打交道,但大家對字符編碼都是一知半解:“天天吃豬肉卻很少見過豬跑”,今天我就把它徹底講透!

    * 推薦閱讀:如果本文太“硬”,就看看這兩篇吧:《史上最通俗,徹底搞懂字符亂碼問題的本質(zhì)》、《字符編碼那點事:快速理解ASCII、Unicode、GBK和UTF-8》。

    技術(shù)交流:

    (本文已同步發(fā)布于:http://www.52im.net/thread-4210-1-1.html

    二、什么是字符編碼?

    我們知道計算機的世界只有0和1,如果沒有字符編碼,我們看到的就是一串"110010100101100111001....",我們的溝通就好像是在對牛彈琴,我看不懂它,它看不懂我。

    字符編碼就好比人類和機器之間的翻譯程序,把我們熟知的字符文字翻譯成機器能讀懂的二進制,同時把二進制翻譯成我們能看懂的字符。

    以下是百科對字符編碼的解釋:

    字符編碼(Character encoding)也稱字集碼,是把字符集中的字符,編碼為指定集合中的某一對象(例如:比特模式、自然數(shù)序列、8位組或者電脈沖),以便文本在計算機中存儲或者通信網(wǎng)絡(luò)的傳遞。常見的例子是將拉丁字母表編碼成摩斯電碼和ASCII,比如ASCII編碼是將字母、數(shù)字和其它符號進行編號,并用7比特的二進制來表示這個整數(shù)。

    字符集(Character set)是多個字符的集合,字符集種類較多,每個字符集包含的字符個數(shù)不同,常見字符集名稱:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。計算機要準(zhǔn)確的處理各種字符集文字,就需要進行字符編碼,以便計算機能夠識別和存儲各種文字。

    三、為什么計算機需要編碼?

    3.1、概述

    編碼(Encode)是信息從一種形式轉(zhuǎn)換為另一種形式的過程,比如用預(yù)先規(guī)定的方法將字符(文字、數(shù)字、符號等)、圖像、聲音或其它對象轉(zhuǎn)換成規(guī)定的電脈沖信號或二進制數(shù)字。

    我們現(xiàn)在看到的一幅幅圖畫,聽到的一首首音樂,甚至我們寫的一行行代碼,敲下的一個個字符,所看到的所聽到的都是那么的真實,但其實在背后都是一串“01”的數(shù)字。你昨天在手機上看到的那個心動女孩,真實世界中并不存在,只是計算機用“01”數(shù)字幫你生成的“骷髏”而已(宅男夢碎。。。)。

    3.2、二進制其實不存在

    你可能認(rèn)為計算機中的數(shù)據(jù)就是“01”二進制,但是實際上計算機中并沒有二進制,即便我們知道所有的內(nèi)容都是存儲在硬盤中,但是你把它拆開可找不到里面有任何“0101”的數(shù)字,里面也只有盤片、磁道。就算我們放大了去看盤片,也只有凹凸不平的盤面,凸起的地方是被磁化過的,凹進去的地方是沒有被磁化的;只是我們給凸起的地方取了個名字叫數(shù)字“1”,凹進的地方取名叫數(shù)字“0”。

    同樣內(nèi)存里你也找不到二進制數(shù)字:內(nèi)存放大了看就是一堆電容組,內(nèi)存單元存儲的是“0”還是“1”取決于電容是否有電荷,有電荷我們認(rèn)為他是“1”,無電荷認(rèn)為他是“0”。但是電容是會放電的,時間一長,代表“1”的電容會放電,代表“0”的電容會吸電,這也是我們內(nèi)存不能斷電的原因,需要定期對電容進行充電,保證“1”的電容電量有電。

    再說顯示器:這個大家感受是最直接的,你透過顯示器看到的美女畫皮、日月山川,其實就是一個個不同顏色的發(fā)光二極管發(fā)出強弱不一的光點,顯示器就是一群發(fā)光二極管組成的矩陣,其中每一個二極管可以被稱為一個像素,“1”表示亮,“0”表示滅,而我們平時能看到五彩的顏色,是把三種顏色(紅綠藍(lán)三原色)的發(fā)光二極管做到了一起。那對于一個ASCII編碼“65”最后又怎么顯示成“A”的呢?這就是顯卡的功勞,顯卡中存儲了每一個字符的圖形數(shù)據(jù)(也稱字形碼),將二維矩陣的圖形數(shù)據(jù)傳給顯示器成像(如下圖所示)。

    因此:所謂的0和1都是電流脈沖信號,二進制其實是我們抽象出來的數(shù)學(xué)邏輯概念。那我們?yōu)槭裁匆枚M制表示?

    因為:二進制只有兩種狀態(tài),使用有兩個穩(wěn)定狀態(tài)的物理器件就可以表示二進制中的每一位,例如用高低電平或電荷的正負(fù)性、燈的亮和滅都可以很方便地用“0”和“1”來表示,這為計算機實現(xiàn)邏輯運算和邏輯判斷提供了便利條件。

    四、計算機編碼轉(zhuǎn)換過程

    4.1、概述

    正因為計算機只能表示“01”的邏輯概念,無法直接表示圖片以及文字,所以我們需要一定的轉(zhuǎn)換過程。

    這其實就是我們按照一定的規(guī)則維護了字符-數(shù)字的映射關(guān)系,比如我們把“A”抽象成計算機中的“1”,當(dāng)我們看到1的時候就認(rèn)為這是“A”,本質(zhì)上就是一張映射表,理論上你可以隨意給每個字符分配一個獨一無二的編號(character code,字符編碼)。

    比如下表這樣:

    接下來我們來看下一個文字從“輸入-轉(zhuǎn)碼存儲-輸出(顯示/打印)”的簡單流程。

    首先:我們知道計算機是美國人發(fā)明的,規(guī)則是美國人定的,鍵盤上的按鍵也都是英文字母,所以編號不是你想怎么分配就怎么分配。對于英文字母的輸入,鍵盤和ASCII碼之間是直接對應(yīng)的,鍵盤按鍵“A”對應(yīng)的編號“65”,存儲到磁盤上也是“65”的二進制直譯“01000001”,這很好理解。

    但是:對于漢字輸入就不是這么回事了,鍵盤上可沒有漢字對應(yīng)的輸入按鍵,我們不可能直接敲出漢字字符。于是就有了輸入碼、機內(nèi)碼、字形碼的轉(zhuǎn)換關(guān)系,輸入碼幫助我們把英文鍵盤按鍵轉(zhuǎn)換成漢字字符,機內(nèi)碼幫助我們把漢字字符轉(zhuǎn)換成二進制序列,字形碼幫助我們把二進制序列輸出到顯示器成像。

    4.2、輸入碼

    我們模擬下漢字的輸入過程。

    首先:打開txt文本敲下“nihao”的拼音字母,然后輸入欄會彈出多個符合條件的漢字詞組,最后我們會選擇相應(yīng)的編號,就能實現(xiàn)漢字的輸入。

    那這過程又是如何實現(xiàn)的呢?

    計算機領(lǐng)域有一句如同摩西十誡般的神圣哲言:“計算機科學(xué)領(lǐng)域的任何問題都可以通過增加一個間接的中間層來解決”。

    這里我們再加一層按鍵字母組合和漢字的映射表,好比英漢字典,這層我們稱為輸入碼,輸入碼到內(nèi)碼的過程就是一次查表轉(zhuǎn)換操作,比如“nihao”這幾個ASCII字符,大家可以隨便修改映射表以及候選編號,我可以把他映射成“你好驍飏”(如下圖所示)。

    4.3、機內(nèi)碼

    機內(nèi)碼也稱內(nèi)碼,是字符編碼最核心的部分。

    機內(nèi)碼是字符集在計算機中實際存儲、交換、通信使用的二進制編碼,通過內(nèi)碼我們可以達到高效率的存儲、傳輸文本的目的。我們的外碼(輸入碼)實現(xiàn)了鍵盤按鍵和字符的映射轉(zhuǎn)換,但是機內(nèi)碼是讓字符真正變成了機器能讀懂的二進制語言。

    4.4、字形碼

    計算機中的字符都是以內(nèi)碼的二進制形式表示,我們怎么把數(shù)字對應(yīng)的字符在顯示器上顯示出來呢,比如數(shù)字“1”代表漢字“你”,怎么把“1”顯示成“你”?

    這就需要依賴字形碼,字形碼本質(zhì)上是一個n*n 的像素點陣,把某些位置的像素設(shè)置為白色(用 1 表示),其它位置像素設(shè)置為黑色(用 0 表示),每一個字符的字形都是預(yù)先存放在計算機內(nèi),而這樣的字形信息庫我們稱為字庫。

    比如中文“你”的點陣圖,這樣一個 16*16 的像素矩陣,需要 16 * 16 / 8 = 32 字節(jié)的空間來表示,右邊的字模信息稱為字形碼。不同的字庫(如宋體、黑體)對同一個字符的字形編碼是不同的。

    所以字符編碼到顯示的字形碼,其實又是另一張查找表,也就是字符編碼-字形碼的映射關(guān)系表。

    其實我們也可以認(rèn)為字符編碼是字形碼的一種壓縮方式,一個占32字節(jié)的像素點陣壓縮成了2字節(jié)的機內(nèi)碼。

    五、字符編碼的歷史

    5.1、電報編碼

    從廣義上來說,編碼的歷史很悠久,一直可以追溯到結(jié)繩記事的遠(yuǎn)古時期,但跟現(xiàn)代字符編碼比較接近的還是摩爾斯電碼的發(fā)明,自此開啟了信息通信時代的大門。

    摩爾斯電碼是由美國人摩爾斯在1837年發(fā)明的,比起ASCII還要早100多年,在早期的無線電上作用非常大,它是每個無線電通訊者需必知的,它的是由點dot “.” 和劃dash “-” 這兩種符號所組成的,電報中表達為短滴和長嗒,跟二進制一樣也是二元碼。

    一個二元肯定不夠表示我們的字母,那么就用多個二元來表示,比如嘀嗒“.-”代表字母“A”,嗒嘀嘀嘀“-...”代表字母“B”。

    摩爾斯電碼表:

    5.2、編碼紀(jì)元

    計算機一開始發(fā)明出來時是用來解決數(shù)學(xué)計算問題的,后來人們發(fā)現(xiàn),計算機還可以做更多的事,例如文本處理等。那個時候的機器都很大,機器之間都是隔離的,沒考慮過機器的通信問題,各大廠商也各干各個的,搞自己的硬件搞自己的軟件,想怎么編碼就怎么編碼。

    后來機器間需要相互通信的時候,發(fā)現(xiàn)在不同計算機上顯示出來的字符不一樣,在IBM上“00010100”數(shù)字代表“A”,跑到微軟系統(tǒng)上顯示成了“B”,大家就傻眼了。于是美國的標(biāo)準(zhǔn)化組織就跑出來制定了ASCII編碼(American Standard Code for Information Interchange),統(tǒng)一了游戲規(guī)則,規(guī)定了常用符號用哪些二進制數(shù)來表示。

    5.3、百花齊放

    統(tǒng)一ASCII 碼標(biāo)準(zhǔn)對于英語國家很開心,但是ASCII編碼只考慮了英文字母,后來計算機傳到歐洲地區(qū),法國人需要加個字母符號(如:é),德國人又需要加幾個字母(Ä ä、Ö ö、ü ü、ß),幸好ASCII只用了前127個編號,于是歐洲人就將ASCII沒用完的編碼(128-255)為自己特有的符號編碼,也能很好的一起玩耍。

    但是等傳到我們中國后,做為博大精深的漢語言就徹底蒙圈了,我們有幾萬個漢字,255個編號完全不夠用啊,所以有了后來的多字節(jié)編碼… 因此,各個國家都推出了本國語言的編碼表,也就有了后來的 ISO 8859 系列、GB系列(GB2312、GBK、GB18030、GB13000)、Big5、EUC-KR、JIS … ,不過為了能在計算機系統(tǒng)中通用,這些擴展的編碼均直接或間接兼容 ASCII 碼。

    而微軟/IBM這些國際化產(chǎn)商為了把自己的產(chǎn)品賣到全世界,就需要支持各個國家的語言,要在不同的地方采用當(dāng)?shù)氐木幋a方式,于是他們就把全世界的編碼方式都集中到一起并編上號,并且起了個名字叫代碼頁(Codepage,又稱內(nèi)碼表),所以我們有時候也會看到xx代碼頁來指代某種字符編碼,比如在微軟系統(tǒng)里 中文GBK編碼對應(yīng)的是936代碼頁,繁體中文 Big5編碼對應(yīng)的是950代碼頁。

    這些既兼容ASCII又互相之間不兼容的字符編碼,后來又統(tǒng)稱為ANSI編碼??吹较旅孢@張圖估計大家就很熟悉了,window下面我們基本上都用ANSI編碼保存。

    ANSI的字面意思并非指字符編碼,而是美國的一個非營利組織,是美國國家標(biāo)準(zhǔn)學(xué)會(American National Standards Institute)的縮寫,ANSI這個組織為字符編碼做了很多標(biāo)準(zhǔn)制定工作,后來大家習(xí)慣把這類混亂的多字節(jié)編碼叫ANSI編碼或者標(biāo)準(zhǔn)代碼頁。

    ANSI編碼只是一個范稱,一般代表系統(tǒng)默認(rèn)的編碼方式,而且并不是確定的某一種編碼方式——比如在Window操作系統(tǒng)里,中國區(qū)ANSI編碼指的是GB編碼,在香港地區(qū)ANSI編碼指的是Big5編碼,在韓國ANSI編碼指的是EUC-KR編碼。

    5.4、天下一統(tǒng)

    由于各個國家各搞各的字符編碼,如果有些人想裝逼中文里飚兩句韓文怎么辦呢?不好意思,你的逼級太高,沒法支持,你選擇了GB2312就只能打出中文字符。同時各大國際廠商在兼容各種字符編碼問題上也深受折磨,于是忍無可忍之下,決定開發(fā)一套能容納全世界所有字符的編碼,就有了后面大名鼎鼎的Unicode。

    Unicode也叫萬國碼,包括字符集、編碼方案等。

    Unicode是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,它為每種語言中的每個字符設(shè)定了統(tǒng)一并且唯一的二進制編碼,在這種語言環(huán)境下,不會再有語言的編碼沖突,在同屏下也可以顯示任何國家語言的內(nèi)容,這就是Unicode的最大好處。

    在Unicode編碼方案里常見的有四種編碼實現(xiàn)方案UTF-7、 UTF-8、UTF-16、UTF-32,最為知名的就是 UTF-8。Unicode設(shè)計之初是采用雙字節(jié)定長編碼的UTF-16,但是發(fā)現(xiàn)歷史包袱太重推不動,最后出了個變長的UTF-8才被廣泛接受。

    六、字符編碼模型

    6.1、傳統(tǒng)編碼模型

    在傳統(tǒng)字符編碼模型中,基本上都是將字符集里的字符用十進制進行逐一的編號,然后把十進制編號直接轉(zhuǎn)成對應(yīng)的二進制碼,可以說該字符編號就是字符的編碼。

    計算機在處理字符與數(shù)字的轉(zhuǎn)換關(guān)系上其實就是查找映射表的過程。

    像ASCII編碼就是給每個英文字符編一個獨一無二的數(shù)字,整個編碼處理過程相對還是比較簡單的,計算機內(nèi)部直接就映射成了二進制,十進制的編號只是方便我們看的。

    6.2、現(xiàn)代編碼模型

    Unicode編碼模型采用了一個全新的編碼思路,將編碼模型劃分為4 個層次(也有說5個層次的),不過第五層是傳輸層的編碼適配,放在編碼模型里嚴(yán)格來說不是很恰當(dāng)。

    這 4 個層次分別是:

    • 1)第一層,抽象字符集 ACR(Abstract Character Repertoire):定義抽象字符集合,明確各個抽象字符;
    • 2)第二層,編號字符集 CCS(Coded Character Set):將抽象字符集進行數(shù)字編號;
    • 3)第三層,字符編碼方式 CEF(Character Encoding Form):將字符編號編碼為邏輯上的碼元序列;
    • 4)第四層,字符編碼方案 CES(Character Encoding Scheme):將邏輯上的碼元序列編碼為物理字節(jié)序列。

    下面將分別來詳細(xì)講講各層。

    6.3、第一層:抽象字符集 ACR

    所謂抽象字符集,就是抽象字符的合集。

    它是一個無序集合,這里強調(diào)了字符是抽象的,也就是不僅包括我們視覺上能看到的狹義字符,比如“a”這樣的有形字符,也包括一些我們看不到的無形字符,比如一些控制字符“DELETE”、“NULL”等。

    抽象的另一層含義是有些字形是由多個字符組合成的,比如西班牙語的 “ñ” 由“n”和“~”兩個字符組成,這一點上 Unicode 和傳統(tǒng)編碼標(biāo)準(zhǔn)不同,傳統(tǒng)編碼標(biāo)準(zhǔn)多是將 ñ 視作一個獨立的字符,而 Unicode 中將其視為兩個字符的組合。

    同時一個字符也可能會有多種視覺上的字形表示,比如一個漢字有楷、行、草、隸等多種形體,這些都視為同一個抽象字符(即字符集編碼是對字符而非字形編碼),如何顯示是字形庫的事。

    漢字“人”的不同形態(tài):

    抽象字符集有開放與封閉之分:開放的字符集指還會不斷新增字符的字符集,封閉字符集是指不會新增字符的字符集。比如ASCII就是封閉式的,只有128個字符,以后也不會再加,但是Unicode是開放式的,會不斷往里加新字符的,已經(jīng)從最初的 7163 個增加到現(xiàn)在的144,697 個字符。

    6.4、第二層:編號字符集 CCS

    編號字符集就是對抽象字符集里的每個字符進行編號,映射到一個非負(fù)整數(shù)的集合

    編號一般用方便人類閱讀的十進制、十六進制來表示,比如“A”字符編號“65”,“B”字符編號是“66”。

    大家需要清楚對于有些字符編碼的編號就是存儲的二進制序列,如ASCII編碼;有些字符編碼的編號跟存儲的二進制序列并不一樣,比如GB2312、Unicode等。

    另外:編號字符集合是有范圍限制的,比如ASCII字符集范圍是0~127,ISO-8859-1范圍是0~256,而GB2312是用一個94*94的二維矩陣空間來表示,Unicode是用Plane平面空間的概念來表示,這稱為字符集的編號空間。

    編號空間中的一個位置稱為碼點( Code Point 代碼點 )。一個字符占用的碼點所在的坐標(biāo)(非負(fù)整數(shù)值對)或所代表的非負(fù)整數(shù)值,就是該字符的碼值(碼點編號)。

    ASCII碼點編號:

    6.5、第三層:字符編碼方式 CEF

    抽象字符集和編號字符集是站在方便我們理解的角度來看的,所以最后我們需要翻譯成計算機能懂的語言,將十進制的編號轉(zhuǎn)換成二進制的形式。

    因此:字符編碼方式就是將字符集的碼點編號,轉(zhuǎn)換成二進制碼元序列( Code Unit Sequence )的過程。

    碼元:字符編碼的最小處理單元,比如ASCII一個字符等于一個字節(jié),屬于單字節(jié)碼元;UTF-16一個字符等于兩個字節(jié),處理過程是按字“word”來處理,所以是雙字節(jié)碼元;UTF-8是多字節(jié)編碼,有單字節(jié)字符,也有多字節(jié)字符,每次處理是按單個單個字節(jié)解析處理,所以處理最小單位是字節(jié),也屬于單字節(jié)碼元。

    這里大家可能會有疑問:十進制直接轉(zhuǎn)二進制不就好了嗎,為什么要單獨抽出這么一層?

    早期的字符編碼確實也是這么處理的,十進制和二進制之間是直接轉(zhuǎn)換過去的,比如ASCII碼,字符“A”的十進制是“65”,那對應(yīng)的二進制就是“1000001”,同時存儲到硬盤里的也是這個二進制,所以那時候的編碼比較簡單。

    隨著后來多字節(jié)字符編碼(Muilti-Bytes Character Set,MBCS多字節(jié)字符集)的出現(xiàn),字符編號和二進制之間不是直接轉(zhuǎn)換過去的,比如GB2312編碼,“萬”字的區(qū)位編號是“45,82”,對應(yīng)的二進制機內(nèi)碼卻是“1100 1101 1111 0010”(其十進制是“205,242”)。

    如果這里不轉(zhuǎn)換直接映射成二進制碼會出什么問題呢?“萬”字的字符編號“45,82”,45在ASCII里是“-”,82是“U”,那到底是顯示兩個字符“-U”還是顯示一個字符“萬”字,為了避免這種沖突 所以增加了前綴處理,詳細(xì)的過程會在下文具體來講解。

    6.6、第四層:字符編碼方案 CES

    字符編碼方案也稱作“序列化格式“( Serialization Format ),指的是將字符編號進行編碼之后的碼元序列映射為字節(jié)序列(即字節(jié)流)的形式,以便經(jīng)過編碼后的字符能在計算機中進行處理、存儲和傳輸。

    字符編碼方式CEF有點像我們數(shù)據(jù)庫結(jié)構(gòu)設(shè)計里的邏輯設(shè)計,而這一層編碼方案CES就像是物理設(shè)計了,將碼元序列映射為跟特定的計算機系統(tǒng)平臺相關(guān)的物理意義上的二進制過程。

    這里大家可能又會有疑問:為什么二進制的碼元序列和實際存儲的二進制又會不一樣呢?

    這主要是計算機的大小端序造成的,具體端序內(nèi)容會在UTF-16編碼部分詳細(xì)介紹。

    “大小端序名詞”出自Jonathan Swift的《格列夫游記》一書 :

    所有人都認(rèn)為,吃雞蛋前,原始的方法是打破雞蛋較大的一端??墒钱?dāng)今皇帝的祖父小時候吃雞蛋,一次按古法打雞蛋時碰巧將一個手指弄破了,因此他的父親,當(dāng)時的皇帝,就下了一道敕令,命令全體臣民吃雞蛋時打破雞蛋較小的一端,違令者重罰。

    老百姓們對這項命令極為反感。歷史告訴我們,由此曾發(fā)生過六次叛亂,其中一個皇帝送了命,另一個丟了王位…關(guān)于這一爭端,曾出版過幾百本大部著作,不過大端派的書一直是受禁的,法律也規(guī)定該派的任何人不得做官。

    ▲ 圖片引用自《面試必考,史上最通俗大小端字節(jié)序詳解

    對大小端字節(jié)序問題感興趣的可以詳讀:《腦殘式網(wǎng)絡(luò)編程入門(九):面試必考,史上最通俗大小端字節(jié)序詳解》一文。

    七、常見字符編碼1:ASCII

    很久以前:計算機制造商都是按各自的方式來將字符渲染到屏幕上,當(dāng)時的計算機動不動可就是一套房子的大小,這家伙可不是誰都能玩的起的,那時人們并不關(guān)心計算機如何交流。

    隨著上世紀(jì)七八十年代微處理器的出現(xiàn),計算機變得越來越小,個人計算機開始進入大眾的視線,隨后出現(xiàn)了井噴式的發(fā)展,但是之前廠商都是各自為政,沒考慮過自家的產(chǎn)品要兼容別人家的東西,導(dǎo)致在不同計算機體系間的數(shù)據(jù)轉(zhuǎn)換變得十分蛋疼,因此美國的標(biāo)準(zhǔn)協(xié)會在1967年制定出了ASCII編碼,到目前為止共定義了128個字符。

    ASCII 編碼:

    (注意:該表是列表示字節(jié)高 4 位。上圖引用自《字符編碼那點事:快速理解ASCII、Unicode、GBK和UTF-8》)

    其中:前 32 個(0~31)是不可見的控制字符,32~126 是可見字符,127 是 DELETE 命令(鍵盤上的 DEL 鍵)。

    其實:早在ASCII之前,IBM在1963年也推出過一套字符編碼系統(tǒng)EBCDIC,跟ASCII碼一樣囊括了控制字符、數(shù)字、常用標(biāo)點、大小寫英文字母。

    EBCDIC 編碼:

    但是:他的字符編號并不是連續(xù)的,這給后續(xù)程序處理帶來了麻煩,后來ASCII 編碼吸取了 EBCDIC 的經(jīng)驗教訓(xùn),給英文單詞分配了連續(xù)的編碼,方便程序處理,因此被后來廣泛接受。

    ASCII 和 EBCDIC 編碼相比:除了字符連續(xù)排列之外,最大的優(yōu)點是ASCII 只用了一個字節(jié)的低 7 位,最高位永遠(yuǎn)是 0。可別小看了這個最高位的 0,看似無足輕重,但這是ASCII設(shè)計最成功的地方,后面介紹各編碼原理的時候你會發(fā)現(xiàn),正是因為這個高位0,其它編碼規(guī)范才能對 ASCII 碼無縫兼容,使得 ASCII 被廣泛接受。

    八、常見字符編碼2:ISO-8859系列

    美國市場雖然統(tǒng)一了字符編碼,但是計算機制造商在進入歐洲市場的時候又遇到了麻煩。。。

    歐洲的主流語言雖然也是用拉丁字母,但卻存在很多擴展體,比如法語的“é”,挪威語中的“Å”,都無法用 ASCII 表示。但是大家發(fā)現(xiàn)ASCII后面的128個還沒有被使用可以利用起來,這對于歐洲主流語言就足夠了。

    于是就有了大家所熟知的這個ISO-8859-1(Latin-1),它只是擴展了ASCII后128個字符,還是屬于單字節(jié)編碼。同時為了兼容原先的 ASCII碼,當(dāng)最高位是0的時候仍然表示原先的 ASCII 字符不變,當(dāng)最高位是1的時候表示擴展的歐洲字符。

    但是到這里還沒有完:剛說了這只是歐洲主流的語言,但主流語言里沒有法語使用的 œ、Œ、Ÿ 三個字母,也沒有芬蘭語使用的 Š、š、?、? ,而單字節(jié)編碼里的256個碼點都被用完了,于是就出現(xiàn)了更多的變種 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、概述

    當(dāng)計算機進入東亞國家的時候,廠商們更傻眼了,美國和歐洲國家語言基本都是表音字符,一個字節(jié)就足夠用了,但亞洲國家有不少是表意字符,字符個數(shù)動輒幾萬十幾萬的,一個字節(jié)完全不夠用。

    所以我們國家有關(guān)部門按照ISO規(guī)范設(shè)計了GB2312雙字節(jié)編碼。但是GB2312是一個封閉字符集,只收錄了常用字符總共也就7000多個字符,因此為了擴充更多的字符包括一些生僻字,才有了之后的GBK、GB18030、GB13000(“GB” 為 “國標(biāo)” 的漢語拼音首字母縮寫)。

    按照 GB 系列編碼方案,在一段文本中,如果一個字節(jié)是 0~127,那么這個字節(jié)的含義與 ASCII 編碼相同,否則,這個字節(jié)和下一個字節(jié)共同組成漢字(或是 GB 編碼定義的其他字符),所以GB系列都是兼容ASCII編碼的。

    9.2、GB2312

    GB2312是使用兩個字節(jié)來表示漢字的編碼標(biāo)準(zhǔn),共收入漢字6763個和非漢字圖形字符682個。

    為了避免與 ASCII 字符編碼(0~127)相沖突,規(guī)定表示一個漢字的編碼字節(jié)其值必須大于127(即字節(jié)的最高位為 1 ),并且必須是兩個大于 127 的字節(jié)連在一起來共同表示一個漢字( GB2312 為雙字節(jié)編碼),所以GB2312 屬于變長編碼,當(dāng)是英文字符的時候占一個字節(jié),中文字符的時候占兩個字節(jié),可以認(rèn)為 GB2312是對 ASCII 的中文擴展。

    GB2312字符集編號空間是一個94*94的二維表,行表示區(qū)(高位字節(jié)),列表示位(低位字節(jié)),每區(qū)有94個位,每個區(qū)位對應(yīng)一個字符,稱為區(qū)位碼。區(qū)位碼上加2020H,就得到國標(biāo)碼,國標(biāo)碼上加8080H,就得到常用的計算機機內(nèi)碼。

    這里引入了區(qū)位碼、國標(biāo)碼、機內(nèi)碼概念,下面我們說下三者的關(guān)系。

    9.2.1國標(biāo)碼

    國標(biāo)碼是我國漢字信息交換的標(biāo)準(zhǔn)編碼,規(guī)定由4位16進制數(shù)組成,用兩個低7位字節(jié)表示,為了避開 ASCII 字符中的前32個控制指令字符,所以每個字節(jié)都是從第33個編號開始。

    如下圖所示:

    9.2.2區(qū)位碼

    由于上述國標(biāo)碼的16進制可編碼區(qū)不夠直觀不方便我們使用,所以我們把他映射成了十進制的94*94二維表編號空間,我們稱之為區(qū)位碼,同時區(qū)位碼也可以當(dāng)成一種外碼使用,輸入法可以直接切換成區(qū)位碼進行漢字輸入。

    不過這種輸入法無規(guī)則可言 人們很難記住區(qū)位編號,用的人也不多了。

    下圖是區(qū)位碼的二維表,比如“萬”字是45 區(qū) 82 位,所以“萬” 字的區(qū)位碼是“45,82”。

    其中:

    • 1)01~09區(qū)(682個):特殊符號、數(shù)字、英文字符、制表符等(包括拉丁字母、希臘字母、日文平假名及片假名字母、俄語西里爾字母等在內(nèi)的682個全角字符);
    • 2)10~15區(qū):空區(qū),留待擴展;
    • 3)16~55區(qū)(3755個):常用漢字(也稱一級漢字),按拼音排序;
    • 4)56~87區(qū)(3008個):非常用漢字(也稱二級漢字),按部首/筆畫排序;
    • 5)88~94區(qū):空區(qū),留待擴展。

    9.2.3機內(nèi)碼

    GB2312國標(biāo)碼規(guī)范是覆蓋掉ASCII中可見部分的符號和英文字母,使用兩個7位碼將其中的英文字母和符號重新編入。

    但是這樣產(chǎn)生一個弊端:早期用ASCII碼編碼的英文文章無法打開,一打開就是亂碼,也就是說應(yīng)該要兼容早期ASCII碼而不是覆蓋它。

    后來微軟為了解決這個問題:將字節(jié)的最高位設(shè)為1,因為ASCII中使用7位,最高位為0,轉(zhuǎn)換后的編碼稱為機內(nèi)碼(內(nèi)碼),這種方式本質(zhì)上是修改了GB2312的編碼標(biāo)準(zhǔn),最后被大家接受沿用。

    總結(jié)下三者轉(zhuǎn)換關(guān)系:區(qū)位碼 ---> 區(qū)碼和位碼分別 + 32(即 + 20H )得到國標(biāo)碼 ---> 再分別 + 128(即 + 80H)得到機內(nèi)碼(與 ACSII 碼不再沖突)。

    9.3、GBK

    GBK即“國標(biāo)擴展”的意思,因為GB2312雙字節(jié)的最高位都要求大于1,上限也不會超過1萬個字符,所以對此進行了擴展,對GB2312的字符不重新編碼直接沿用,因此完全兼容GB2312。

    GBK雖然也是雙字節(jié)編碼,但是只要求第一個字節(jié)大于 127 就固定表示這是一個漢字的開始,正因為如此,GBK的編碼空間比GB2312大很多。

    GBK 整體編碼范圍為 8140-FEFE,首字節(jié)在 81-FE 之間,尾字節(jié)在 40-FE 之間,剔除 xx7F 一條線,總計 23940 個碼位,共收入 21886 個漢字和圖形符號。其中 GBK/1 收錄除 GB 2312 字符外的其他增補字符,GBK/2 收錄 GB2312 字符,GBK/3 收錄 CJK 字符,GBK/4 收錄 CJK 字符和增補字符,GBK/5 為非中文字符,UDC 為用戶自定義字符。

    詳細(xì)如下如所示:

    這里大家可能會有兩個疑問:為什么尾字節(jié)要從40開始,而不是00開始?為什么要排除 FF、xx7F這兩條線的編號?

    GBK的尾字節(jié)編碼高位沒有強制要求是1,當(dāng)高位是0時跟ASCII碼是沖突的,ASCII碼里00-40之間大部分都是控制字符,所以排除控制字符主要是為了防止丟失高字節(jié)導(dǎo)致出現(xiàn)系統(tǒng)性嚴(yán)重后果。

    排除FF是為了兼容GB2312,GB2312這個位是保留不使用的;而7F表示DEL字符就是向后刪除一個字符,如果傳輸過程中丟失首字節(jié)那么就會出現(xiàn)嚴(yán)重的后果,所以需要將xx7F也排除,這是所有編碼方案都需要注意的地方。

    9.4、GB18030

    隨著計算機的發(fā)展,GBK的2萬多個字符也還是扛不住。

    于是2000年我國又制定了新標(biāo)準(zhǔn) GB18030,用來替代 GBK 標(biāo)準(zhǔn)。GB18030是強制性標(biāo)準(zhǔn),現(xiàn)在在中國大陸銷售的軟件都支持 GB18030。

    GB18030其實是對齊Unicode標(biāo)準(zhǔn)的,里面包括了所有Unicode字符集,也算是Unicode的一種實現(xiàn)(UTF)。

    那既然有了UTF我們?yōu)槭裁催€要搞一套Unicode實現(xiàn)?

    主要是UTF-8/UCS-2他們是不兼容GB2312的,如果直接升級那么就全亂碼了,所以GB18030是為了兼容GB系列,是GBK、GB2312的超集,當(dāng)我們原先的GB2312(GBK)軟件考慮升級到國際化Unicode時,可以直接使用GB18030進行升級。

    GB18030雖然也是GB2312的擴展,但它和GBK的擴展方式不一樣,GBK主要是充分利用了GB2312的一些沒定義的編碼空間,而GB18030采用的是字節(jié)變長編碼,單字節(jié)區(qū)兼容ASCII、雙字節(jié)區(qū)兼容GBK、四字節(jié)區(qū)對齊所有Unicode 碼位。

    實現(xiàn)原理上主要是采用第二字節(jié)未使用到的0x30~0x39編碼空間來判斷是否四字節(jié)。

    具體就是:

    • 1)單字節(jié),其值從0到0x7F。
    • 2) 雙字節(jié),第一個字節(jié)的值從0x81到0xFE,第二個字節(jié)的值從0x40到0xFE(不包括0x7F)。
    • 3) 四字節(jié),第一個字節(jié)的值從0x81到0xFE,第二個字節(jié)的值從0x30到0x39,第三個字節(jié)的值從0x81到0xFE,第四個字節(jié)的值從0x30到0x39。

    十、常見字符編碼4:UNICODE

    10.1、背景介紹

    在統(tǒng)一碼之前,各國創(chuàng)造了大量的節(jié)編碼標(biāo)準(zhǔn),有單字節(jié)的、雙字節(jié)的(如 GB 2312、Shift JIS、Big5 、ISO8859等),各自又相互不兼容。在1987 年,蘋果、Sun、微軟等公司開始討論囊括全世界所有字符的統(tǒng)一編碼標(biāo)準(zhǔn),組成了 Unicode 聯(lián)盟,這個期間做了很多研討工作,討論核心要點如下。

    1)目前世界上有多少個字符,需要幾個字節(jié)存儲?

    工作組統(tǒng)計了當(dāng)時全世界的報紙等刊物,結(jié)論是兩個字節(jié)足以囊括全世界有實用意義的字符(當(dāng)然這只統(tǒng)計了當(dāng)前使用的字符,不包括古代語言或者廢棄語言)。

    2)采用固定長度編碼還是變長編碼?

    一種采用變長編碼形式,對于 ASCII 字符使用一個字節(jié),其他字符使用兩個字節(jié),類似 GBK。另一種采用定長編碼形式,不管是不是 ASCII 字符統(tǒng)一使用兩個字節(jié)。

    方案選擇上主要從計算機處理過程中的時間和空間兩個維度,也就是編解碼的執(zhí)行效率和存儲大小兩方面。

    最后結(jié)論是采用雙字節(jié)定長編碼,因為定長帶來的空間變大在整體傳輸、存儲成本上其實影響并不大,而定長編碼處理效率會明顯高于變長編碼,所以早期 Unicode 采用了定長編碼形式。

    3)中、日、韓中有很多相近的表意文字是否可以統(tǒng)一?

    由于漢字表意文字字符量較大,如果可以統(tǒng)一那么能大幅減少收錄漢字的數(shù)量。

    所以最初收錄漢字遵循兩個基本原則:表意文字認(rèn)同原則和字源分離原則。

    所謂表意文字認(rèn)同原則:即“只對字,不對形”編碼,將同一字的不同字形(即異體字)合并。例如“房”字的第一筆,在中日韓的寫法都不同,但它本身是同一個字,只給一個編碼,而寫法的不同交由字體進行區(qū)分。

    字源分離原則:是指一個字源中同時收錄了同一個字的不同字形,則給予兩個字形分別編碼。例如:之前GBK中就收錄了“戶”、“戶”、“戸”三個字,那么Unicode也需要保留三個字,如果直接合并會造成使用上的困擾。

    例如下面這句話如果不做字源分離,會是什么情況呢?

    原句 :戶有三種寫法,分別是“戶”、“戶”、“戸”,

    改寫后:戶有三種寫法,分別是“戶”、“戶”、“戶”

    10.2、Unicode介紹

    Unicode 稱為統(tǒng)一碼(也叫萬國碼),是按現(xiàn)代編碼模型進行設(shè)計的一套字符編碼體系,涵蓋抽象字符集、編號、邏輯編碼、編碼實現(xiàn)。

    Unicode是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,在這種語言環(huán)境下,不會再有語言的編碼沖突,可以在同屏下顯示任何國家的語言。

    UTF-n編碼(Unicode Transformation Format Unicode字符集轉(zhuǎn)換格式,n表示碼元位數(shù))是Unicode這套編碼體系里的編碼實現(xiàn)CES部分,像UTF-8、UTF-16、UTF-32都是將數(shù)字轉(zhuǎn)換到實際的二進制編碼實現(xiàn),Unicode的編碼實現(xiàn)除了UTF系列之外,還有UCS-2/4,GB18030等。但是現(xiàn)在很多人誤把Unicode當(dāng)成只是一個字符編號,這其實是不對的。

    Unicode可以容納世界上所有國家的文字和符號,其編號范圍是0-0x10FFFF,有1,114,112個碼位,為了方便管理劃分成17個平面,現(xiàn)已定義的碼位有238,605個,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面0又稱為基本多語言平面(Basic Multilingual Plane,簡稱BMP),這個平面基本涵蓋了當(dāng)今世界上正在使用中的常用字符。我們平常用到的字符,一般都是位于 BMP 平面上的,其范圍擁有 65,536 個碼點,其他平面統(tǒng)稱增補平面,關(guān)于平面的概念會在UTF-16章節(jié)詳細(xì)介紹。

    10.3、與UCS的關(guān)系

    說起Unicode我們不得不提UCS(全稱Universal Multiple-Octet Coded Character Set 通用多八位編碼字符集),國際標(biāo)準(zhǔn)編號ISO/IEC 10646,是由 ISO 和 IEC 兩家國際標(biāo)準(zhǔn)組織聯(lián)合成立的工作組設(shè)計的一套新的統(tǒng)一字符集項目,目的與Unicode 聯(lián)盟一樣致力于開發(fā)一款全世界通用的編碼集。

    早在1984 年ISO 和 IEC 兩家組織就成立了一個聯(lián)合工作組來設(shè)計一套新的統(tǒng)一字符集標(biāo)準(zhǔn),但是這兩個組織都不知道對方的存在,直到Unicode聯(lián)盟1988年發(fā)布了Unicode草案(UCS草案1989年發(fā)布),才發(fā)現(xiàn)大家在做同一件事,沒有必要搞兩套標(biāo)準(zhǔn) 所以后面又考慮合并。

    由于UCS 最初設(shè)計的是 31 位編碼空間(UCS-4編碼實現(xiàn)),可以容納 2^31 約 21 億個字符,而Unicode是16位空間(UTF-16編碼實現(xiàn)),所以最開始Unicode 打算作為 UCS 的真子集,即 Unicode 中的每個字符都存在于 UCS 中,而且兩者的碼點相同,但 UCS 中的字符(編號超過65,536的)則不一定存在于 Unicode 中。

    不過:由于雙方利益關(guān)系并沒有說誰解散誰,最后雙方作出一些妥協(xié)保持一致共同發(fā)展,兩個標(biāo)準(zhǔn)中相同字符的編碼(碼點)必須是一樣的。這是一個屁股決定腦袋的決策,如果最初Unicode知道UCS的存在,就不會再出現(xiàn)Unicode了。

    當(dāng)然合并工作不是一蹴而就的而是經(jīng)過多輪迭代, ISO/IEC 和 Unicode在 1993 年發(fā)布了第一版相互兼容版本,到了 1996年Unicode 2.0標(biāo)準(zhǔn)發(fā)布時,Unicode 字符集和 UCS 字符集(即 ISO/IEC 10646-1 )基本保持了一致,同時Unicode為了跟UCS的四字節(jié)保持一致推出了UTF-32編碼實現(xiàn),UCS為了跟Unicode的兩字節(jié)保持一致推出了UCS-2編碼實現(xiàn)。

    所以:現(xiàn)在我們可以認(rèn)為UCS和Unicode是同一個東西,比如我們常見的java內(nèi)部運行就采用的是UTF-16編碼,而window操作系統(tǒng)采用的是UCS-2,他們都是同一個Unicode標(biāo)準(zhǔn)。

    為什么這里使用的是2字節(jié)編碼,而不是4字節(jié)呢?先留個懸念,后續(xù)會詳細(xì)講解。

    10.4、UTF-16(Java內(nèi)部編碼)

    UTF是Unicode Transfer Format的縮寫,即把Unicode轉(zhuǎn)做某種格式的意思,所以UTF-16是Unicode編碼里的其中一種實現(xiàn)方式,16代表的是字節(jié)位數(shù),占兩個字節(jié)(UTF-32則表示4個字節(jié))。

    Unicode 設(shè)計之初是采用UTF-16這種雙字節(jié)定長編碼的,其字符編號就是對應(yīng)的二進制編號,也就是說第二層的CCS和第三層的CEF是一致的。比如漢字“萬”的 Unicode 碼點是 “U+4E07”,其二進制序列就是直譯的“0100 1110 0000 0111 ”,這種編碼方式的優(yōu)點是高效,不需要檢查標(biāo)志位,但缺點是不兼容ASCII,ASCII編碼的文本都會顯示亂碼。

    不過:后來Unicode聯(lián)盟發(fā)現(xiàn) 16 位編碼空間根本不夠用,與此同時 ISO/IEC組織也覺得 UCS的 32 位編碼空間太多了,實際中根本沒有幾十億字符,也挺浪費空間的。

    所以最終 Unicode 聯(lián)盟和 ISO/IEC 工作組達成一致:兩者使用統(tǒng)一的編碼空間“ 0000 ~ 10FFFF”(即 UCS 保證永遠(yuǎn)不分配大于 10FFFF 的字符碼點),而且雙方在字符編碼上保持同步,即一方標(biāo)準(zhǔn)中增加了字符,也要通知另一方同步。

    于是:Unicode在UTF-16基礎(chǔ)上拓展編碼空間到 21 位,UCS則搞了一個雙字節(jié)的UCS-2編碼實現(xiàn)。

    UTF-16 編碼是雙字節(jié)的,上限也只有6w多個碼點,怎么讓他支持到10FFFF(100w+)個碼點呢?

    本質(zhì)就是:多加幾個字節(jié)來表示更多的字符,只是UTF-16不像UCS那樣采用定長4字節(jié),而是使用變長的形式,但是這個跟UTF-8變長方式又不太一樣,他是采用代理對的方式實現(xiàn),大部分常用字符用一個碼元表示(定長2個字節(jié)),其他擴展的特殊字符用兩個碼元表示(定長4字節(jié))。

    10.4.1代理對

    UTF-16跟UTF-8、GB系列等都算是變長字節(jié),但是設(shè)計初衷卻不一樣,像GBK是為了兼容ASCII,但是UTF-16一開始就沒考慮要兼容ASCII,所以他的變長是為了節(jié)約存儲空間而采用的自然增長方案,當(dāng)空間不夠的時候增長到4個字節(jié)。

    那問題來了,我怎么知道存儲的4個字節(jié)是表示一個字符,還是兩個字符呢?比如當(dāng)程序遇到字節(jié)序列01001110 00101101 01010110 11111101時,到底是判斷成一個字符還是兩個字符?

    這就需要一個前導(dǎo)識別,比如GB2312識別第一個字節(jié)高位是不是1來判斷是單字節(jié)還是雙字節(jié),但是UTF-16的高位1已經(jīng)被用來編碼了,當(dāng)然這也難不倒我們,第一位被用了那么就用前幾位的組合形式。

    UTF-16采用了代理對來解決,也就是高半?yún)^(qū)編碼(前兩個字節(jié))范圍D800-DBFF(稱為代理碼點),低半?yún)^(qū)編碼(后兩個字節(jié))范圍DC00-DFFF,組成一個四個字節(jié)表示的字符。

    上述前導(dǎo)6位組合也是有講究的,ISO組織要求編號范圍是0~10FFFF,也就是說用20位就可以表示10FFFF個字符,對于雙碼元就是每個碼元各自負(fù)責(zé)10位,一個碼元是16位,數(shù)字位占去10位后,剩下的6位做為前導(dǎo)位。

    當(dāng)UTF-16使用一個碼元表示的時候,Unicode字符編號跟碼元序列是等值映射的,但是當(dāng)采用雙碼元后,字符編號跟碼元序列就需要轉(zhuǎn)換了。

    下面是碼元和Unicode編號值之間的計算公式。

    換算碼元序列(CH高半?yún)^(qū)/CL低半?yún)^(qū)):

    換算字符編號(CH高半?yún)^(qū)/CL低半?yún)^(qū)):

    10.4.2平面空間

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

    下面是各個平面的用途:

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

    UTF-16可看成是UCS-2的父集。在沒有輔助平面前,UTF-16與UCS-2所指的是同一的意思。但當(dāng)引入輔助平面字符后,就稱為UTF-16了。

    10.4.3字節(jié)序

    字節(jié)序顧名思義是指字節(jié)的順序,對于單字節(jié)編碼來說,一個字符對應(yīng)一個字節(jié),也就不存在字節(jié)序問題。但是對于UTF-16這種定長多字節(jié)編碼,就有字節(jié)順序問題了。

    字節(jié)序其實跟操作系統(tǒng)和底層硬件有關(guān),不僅只是UTF-16這種多字節(jié)編碼存在字節(jié)序,只要是多字節(jié)類型的數(shù)據(jù)都存在字節(jié)順序問題,比如short、int、long。

    為了方便說明,我們這里舉個例子:比如存一個整數(shù)值“305419896”對應(yīng)16進制是0x12345678,有人習(xí)慣從左到右按順序去存,也有人說高位當(dāng)然要放到高位地址而低位放到低位地址,要從右往左存。

    于是就有了下面兩種存取方式:

    其實這兩種方式?jīng)]有孰優(yōu)孰劣,只是我們認(rèn)知習(xí)慣有所不同 最終的設(shè)計不同,說來這都是阿拉伯人的鍋啊,為什么數(shù)字高位非要在左邊,這也引起了著名的大小端之爭。

    因此字節(jié)序也就有了大端和小端的概念,也形成了各自的陣營,比如Windows、FreeBSD、Linux 是小端序,Mac是大端序。其實大小端序并沒有技術(shù)上的好壞之分。

    小端序(Little-Endian):就是低位字節(jié)(即小端字節(jié)、尾端字節(jié))存放在內(nèi)存的低地址,而高位字節(jié)(即大端字節(jié)、頭端字節(jié))存放在內(nèi)存的高地址。

    大端序(Big-Endian ):就是高位字節(jié)(即大端字節(jié)、頭端字節(jié))存放在內(nèi)存的低地址,低位字節(jié)(即小端字節(jié)、尾端字節(jié))存放在內(nèi)存的高地址。

    ▲ 圖片引用自《面試必考,史上最通俗大小端字節(jié)序詳解

    對大小端字節(jié)序問題感興趣的可以詳讀:腦殘式網(wǎng)絡(luò)編程入門(九):面試必考,史上最通俗大小端字節(jié)序詳解》一文。

    10.5、UTF-8

    10.5.1概述

    Unicode還是UCS最初都是采用多字節(jié)定長編碼,由于沒有兼容現(xiàn)有的 ASCII 標(biāo)準(zhǔn)的文件和軟件,新標(biāo)準(zhǔn)很難被推廣,于是兼容ASCII版本的UTF-8就誕生了。

    UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字符編碼,是現(xiàn)代字符編碼模型中的第三層 CEF 。它可以用一至四個字節(jié)對 Unicode 字符集中的所有有效編碼點進行編碼,屬于Unicode標(biāo)準(zhǔn)的一部分,UTF-8 就是為了解決向后兼容 ASCII 碼而設(shè)計,Unicode 中前 128 個字符(與 ASCII 碼一一對應(yīng)),使用與 ASCII 碼相同的二進制值的單個字節(jié)進行編碼,這使得原來處理 ASCII 字符的軟件無須或只須做少部分修改,即可繼續(xù)使用。因此,它逐漸成為電子郵件、網(wǎng)頁及其他存儲或發(fā)送文字優(yōu)先采用的編碼方式。

    —— 維基百科

    UTF-8需要兼容ASCII,所以也需要有前綴碼來控制,前綴規(guī)則如下:

    • 1)如果首字節(jié)以 0 開頭,則是單字節(jié)編碼(即單個單字節(jié)碼元);
    • 2)如果首字節(jié)以 110 開頭,則是雙字節(jié)編碼(即由兩個單字節(jié)碼元所組成的雙碼元序列);
    • 3)如果首字節(jié)以 1110 開頭,則是三字節(jié)編碼(即由三個單字節(jié)碼元所組成的三碼元序列),以此類推。

    理論上UTF-8變長可以超過4個字節(jié),只是Unicode聯(lián)盟規(guī)范上限是10FFFF,所以UTF-8規(guī)則設(shè)計上也限制了大小。

    10.5.2程序算法

    用文字不太好描述算法結(jié)構(gòu),我們就直接來欣賞一下UTF-8鼻祖寫的這段解析代碼,這是Ken Thompson(B語言、C語言的作者、Unix之父)和 Rob Pike 用一個晚上寫出來的編解碼算法,代碼非常簡短精煉,為了方便閱讀我加了注釋解讀。

    typedefstruct

    {

      intcmask; //前綴碼掩碼

      intcval;  //前綴碼

      intshift; //移動位數(shù)

      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 */

    };

     

    /**

    * 把一個多字節(jié)序列轉(zhuǎn)換為一個寬字符

    *

    * @param p 存放計算后的unicode值

    * @param s 需要解析的UTF-8字節(jié)序列

    * @param n 字節(jié)長度

    * @return 解析的字節(jié)長度

    */

    intmbtowc(wchar_t*p, char*s, size_tn)

    {

      longl;  intc0, c, nc;  Tab *t;

      if(s == 0) return0;

      nc = 0;

      //異常校驗(可不用關(guān)注)

      if(n <= nc) return-1;

      //c0 此處備份一下首字節(jié),后續(xù)需要用到前綴碼

      c0 = *s & 0xff;

      //l 保存 Unicode 結(jié)果

      l = c0;

      /* 遍歷tab,從單字節(jié)結(jié)構(gòu)->2字節(jié)結(jié)構(gòu)->..依次檢查找到對應(yīng)tab */

      for(t=tab; t->cmask; t++) {

        //字節(jié)數(shù)+1,字節(jié)數(shù)和tab結(jié)構(gòu)是對應(yīng)的,也就是當(dāng)nc=1時 tab結(jié)構(gòu)是單字節(jié),nc=2是tab是兩字節(jié)

        nc++;

        /* 判斷前綴碼跟當(dāng)前的tab是否一致, 如果一致計算最終unicode值并返回*/

        if((c0 & t->cmask) == t->cval) {

          //通過 & Unicode有效值掩碼,移除高位前綴碼,得到最終unicode值

          l &= t->lmask;

          //異常校驗

          if(l < t->lval) return-1;

          //保存結(jié)果并反回

          *p = l;

          returnnc;

        }

        //異常校驗

        if(n <= nc) return-1;

        //讀取下個字節(jié);如果上面判斷前綴碼不一致,說明需要再讀取下個字節(jié)

        s++;

        //計算有效位的值,目的是去除UTF-8 編碼從第二個字節(jié)開始的高兩位10

        // 例如 s=10101111、0x80=10000000 計算結(jié)果是00101111,這樣就去除了高位前綴10

        c = (*s ^ 0x80) & 0xFF;

        //異常校驗

        if(c & 0xC0) return-1;

        //重新計算unicode值,根據(jù)UTF-8規(guī)則c只有低 6 位有效,所以通過移位把c填入到l的低6位

        l = (l<<6) | c;

      }

      //返回異常

      return-1;

    }

    10.5.3容錯性

    通過上面的程序我們知道:解析過程是一個字節(jié)一個字節(jié)往下處理的,我們在傳輸過程中如果發(fā)生局部的字節(jié)錯誤、丟失,或者中間有一個字節(jié)規(guī)則對不上,會不會影響整個文本的解析?

    我們先來看下其他編碼的容錯情況:從對于單字節(jié)的ASCII碼來說,丟失一個字節(jié)就丟失一個字符,并不影響后續(xù)文本的內(nèi)容,比如Hello world,丟失b2字節(jié)后內(nèi)容是Hllo world少個e而已。

    我們再來看GB2312這種多字節(jié)編碼:如果丟失了b2字節(jié)那么整個文本都亂套了,這是最糟糕的,大部分多字節(jié)編碼都有類似問題,一旦出現(xiàn)錯誤可能導(dǎo)致整個文件都需要重傳。

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

    十一、本文總結(jié)

    看起來好像誰都懂的字符編碼知識,深入了解之后發(fā)現(xiàn)也有這么濃重的發(fā)展歷程,試想一下,如果計算機還是跟之前大型機一樣,個人計算機沒有井噴式發(fā)展起來就沒有這些字符編碼的事了,如果ASCII當(dāng)初就設(shè)計成多字節(jié)編碼,也沒有后面UNICODE什么事了。

    計算機字符編碼發(fā)展歷程其實就是一個很典型的架構(gòu)設(shè)計問題。

    到底好的架構(gòu)是設(shè)計出來的,還是演化出來的?

    有人說靠演化出來的:沒有設(shè)計的產(chǎn)品架構(gòu)是沒有靈魂的,發(fā)展的路上死的很快。

    有人說靠設(shè)計出來的:這是一種完美主義者,你超前設(shè)計個50年、100年等你設(shè)計出來了,說不定公司都已經(jīng)倒閉了,有很多叫好不叫做的產(chǎn)品、架構(gòu)也比比皆是。

    其實:一個好的架構(gòu)是既要靠設(shè)計又要靠演化,老話說的好三分靠設(shè)計七分靠演化,我們既要學(xué)會務(wù)實,也要懂得前瞻,至少我們首先需要活下來。

    十二、參考資料

    [1] Unicode中文編碼表

    [2] Every Developer Should Know About The Encoding

    [3] 史上最通俗,徹底搞懂字符亂碼問題的本質(zhì)

    [4] 字符編碼那點事:快速理解ASCII、Unicode、GBK和UTF-8

    [5] 面試必考,史上最通俗大小端字節(jié)序詳解

    附錄:阿里技術(shù)文章匯總

    阿里釘釘技術(shù)分享:企業(yè)級IM王者——釘釘在后端架構(gòu)上的過人之處

    現(xiàn)代IM系統(tǒng)中聊天消息的同步和存儲方案探討

    阿里技術(shù)分享:深度揭秘阿里數(shù)據(jù)庫技術(shù)方案的10年變遷史

    阿里技術(shù)分享:阿里自研金融級數(shù)據(jù)庫OceanBase的艱辛成長之路

    來自阿里OpenIM:打造安全可靠即時通訊服務(wù)的技術(shù)實踐分享

    釘釘——基于IM技術(shù)的新一代企業(yè)OA平臺的技術(shù)挑戰(zhàn)(視頻+PPT) [附件下載]

    阿里技術(shù)結(jié)晶:《阿里巴巴Java開發(fā)手冊(規(guī)約)-華山版》[附件下載]

    重磅發(fā)布:《阿里巴巴Android開發(fā)手冊(規(guī)約)》[附件下載]

    作者談《阿里巴巴Java開發(fā)手冊(規(guī)約)》背后的故事

    《阿里巴巴Android開發(fā)手冊(規(guī)約)》背后的故事

    干了這碗雞湯:從理發(fā)店小弟到阿里P10技術(shù)大牛

    揭秘阿里、騰訊、華為、百度的職級和薪酬體系

    淘寶技術(shù)分享:手淘億級移動端接入層網(wǎng)關(guān)的技術(shù)演進之路

    難得干貨,揭秘支付寶的2維碼掃碼技術(shù)優(yōu)化實踐之路

    淘寶直播技術(shù)干貨:高清、低延時的實時視頻直播技術(shù)解密

    阿里技術(shù)分享:電商IM消息平臺,在群聊、直播場景下的技術(shù)實踐

    阿里技術(shù)分享:閑魚IM基于Flutter的移動端跨端改造實踐

    阿里IM技術(shù)分享(三):閑魚億級IM消息系統(tǒng)的架構(gòu)演進之路

    阿里IM技術(shù)分享(四):閑魚億級IM消息系統(tǒng)的可靠投遞優(yōu)化實踐

    阿里IM技術(shù)分享(五):閑魚億級IM消息系統(tǒng)的及時性優(yōu)化實踐

    阿里IM技術(shù)分享(六):閑魚億級IM消息系統(tǒng)的離線推送到達率優(yōu)化

    阿里IM技術(shù)分享(七):閑魚IM的在線、離線聊天數(shù)據(jù)同步機制優(yōu)化實踐

    阿里IM技術(shù)分享(八):深度解密釘釘即時消息服務(wù)DTIM的技術(shù)設(shè)計

    阿里IM技術(shù)分享(九):深度揭密RocketMQ在釘釘IM系統(tǒng)中的應(yīng)用實踐

    (本文已同步發(fā)布于:http://www.52im.net/thread-4210-1-1.html



    作者:Jack Jiang (點擊作者姓名進入Github)
    出處:http://www.52im.net/space-uid-1.html
    交流:歡迎加入即時通訊開發(fā)交流群 215891622
    討論:http://www.52im.net/
    Jack Jiang同時是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
    本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處(也可前往 我的52im.net 找到我)。


    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 国产偷国产偷亚洲高清人| 亚洲日韩乱码久久久久久| 亚洲欧美精品午睡沙发| 免费三级毛片电影片| 337p日本欧洲亚洲大胆精品555588| 一个人看的在线免费视频| 亚洲国产成人久久一区久久| 激情无码亚洲一区二区三区| 国产精品jizz在线观看免费| 久久久久久亚洲精品无码| 啊灬啊灬别停啊灬用力啊免费看| 免费看又黄又爽又猛的视频软件| 亚洲精品线路一在线观看| a一级爱做片免费| 亚洲无码在线播放| 久久美女网站免费| 亚洲成aⅴ人片在线影院八| 久久不见久久见中文字幕免费| 亚洲日韩一中文字暮| 亚洲 综合 国产 欧洲 丝袜 | 亚洲啪AV永久无码精品放毛片| 女人被免费视频网站| 国产亚洲美女精品久久| 亚洲夜夜欢A∨一区二区三区| 日韩精品极品视频在线观看免费 | 国产∨亚洲V天堂无码久久久| 久久99精品视免费看| 亚洲香蕉久久一区二区| 国产又长又粗又爽免费视频 | 亚洲视频中文字幕| 女人与禽交视频免费看| 久久www免费人成看国产片| 亚洲AV一宅男色影视| 91青青青国产在观免费影视| 亚洲人成图片网站| 久久久久亚洲AV无码专区桃色| 日韩精品无码一区二区三区免费| 亚洲国产成人手机在线观看| 黑人大战亚洲人精品一区| 亚洲高清国产拍精品熟女| 久久影视国产亚洲|