多是最詳細的字符編碼詳解

Created By JishuBao on 2019-04-02 12:38:22
Recently revised in 2019-04-03 12:38:22php

 

  歡迎你們來到技術寶的掘金世界,您的star是我寫文章最大的動力!GitHub地址     前端

文章簡介:git

一、趕上emoji程序員

二、字符編碼的故事github

三、字符編碼詳解算法

四、字符編碼區別編程

五、談談emoji微信

1、趕上emoji

最近閒來無事,在看源碼,發現一個頗有意思的事情。網絡

當時就震驚了,這個emoj表情好牛逼啊...直接在編輯器裏面就有表情了,頓時就感受不懂原理,各類字符編碼都理解的雲裏霧裏的,只知道一個utf-8,因此準備搜索大量資料準備把這個字符編碼瞭解通透!

2、字符編碼的故事

好久好久之前,有一羣人,他們決定用8個能夠開合的晶體管來組合成不一樣的狀態,以表示世界上的萬物。他們看到8個開關狀態是好的,因而他們把這稱爲」字節「。再後來,他們又作了一些能夠處理這些字節的機器,機器開動了,能夠用字節來組合出不少狀態,狀態開始變來變去。他們看到這樣是好的,因而它們就這機器稱爲」計算機「。編輯器

開始計算機只在美國用。八位的字節一共能夠組合出256(2的8次方)種不一樣的狀態。 他們把其中的編號從0開始的32種狀態分別規定了特殊的用途,一但終端、打印機趕上約定好的這些字節被傳過來時,就要作一些約定的動做:

趕上0×10, 終端就換行;

趕上0×07, 終端就向人們嘟嘟叫;

趕上0x1b, 打印機就打印反白的字,或者終端就用彩色顯示字母。

他們看到這樣很好,因而就把這些0×20如下的字節狀態稱爲」控制碼」。他們又把全部的空格、標點符號、數字、大小寫字母分別用連續的字節狀態表示,一直編到了第127號,這樣計算機就能夠用不一樣字節來存儲英語的文字了。你們看到這樣,都感受 很好,因而你們都把這個方案叫作 ANSI 的」Ascii」編碼(American Standard Code for Information Interchange,美國信息互換標準代碼)。當時世界上全部的計算機都用一樣的ASCII方案來保存英文文字。

後來,就像建造巴比倫塔同樣,世界各地都開始使用計算機,可是不少國家用的不是英文,他們的字母裏有許可能是ASCII裏沒有的,爲了能夠在計算機保存他們的文字,他們決定採用 127號以後的空位來表示這些新的字母、符號,還加入了不少畫表格時須要用下到的橫線、豎線、交叉等形狀,一直把序號編到了最後一個狀態255。從128 到255這一頁的字符集被稱」擴展字符集「。今後以後,貪婪的人類再沒有新的狀態能夠用了,美帝國主義可能沒有想到還有第三世界國家的人們也但願能夠用到計算機吧!

等中國人們獲得計算機時,已經沒有能夠利用的字節狀態來表示漢字,何況有6000多個經常使用漢字須要保存呢。可是這難不倒智慧的中國人民,咱們不客氣地把那些127號以後的奇異符號們直接取消掉, 規定:一個小於127的字符的意義與原來相同,但兩個大於127的字符連在一塊兒時,就表示一個漢字,前面的一個字節(他稱之爲高字節)從0xA1用到0xF7,後面一個字節(低字節)從0xA1到0xFE,這樣咱們就能夠組合出大約7000多個簡體漢字了。在這些編碼裏,咱們還把數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在 ASCII 裏原本就有的數字、標點、字母都通通從新編了兩個字節長的編碼,這就是常說的」全角」字符,而原來在127號如下的那些就叫」半角」字符了。中國人民看到這樣很不錯,因而就把這種漢字方案叫作 「GB2312「。GB2312 是對 ASCII 的中文擴展

可是中國的漢字太多了,咱們很快就就發現有許多人的人名沒有辦法在這裏打出來,特別是某些很會麻煩別人的國家領導人。因而咱們不得不繼續把GB2312 沒有用到的碼位找出來老實不客氣地用上。後來仍是不夠用,因而乾脆再也不要求低字節必定是127號以後的內碼,只要第一個字節是大於127就固定表示這是一個漢字的開始,無論後面跟的是否是擴展字符集裏的內容。結果擴展以後的編碼方案被稱爲 GBK 標準,GBK包括了GB2312 的全部內容,同時又增長了近20000個新的漢字(包括繁體字)和符號。 後來少數民族也要用電腦了,因而咱們再擴展,又加了幾千個新的少數民族的字,GBK擴成了 GB18030。今後以後,中華民族的文化就能夠在計算機時代中傳承了。 中國的程序員們看到這一系列漢字編碼的標準是好的,因而通稱他們叫作 「DBCS「(Double Byte Charecter Set 雙字節字符集)。在DBCS系列標準裏,最大的特色是兩字節長的漢字字符和一字節長的英文字符並存於同一套編碼方案裏,所以他們寫的程序爲了支持中文處理,必需要注意字串裏的每個字節的值,若是這個值是大於127的,那麼就認爲一個雙字節字符集裏的字符出現了。那時候凡是受過加持,會編程的計算機僧侶們都要天天念下面這個咒語數百遍: 「一個漢字算兩個英文字符!一個漢字算兩個英文字符……

由於當時各個國家都像中國這樣搞出一套本身的編碼標準,結果互相之間誰也不懂誰的編碼,誰也不支持別人的編碼,連大陸和臺灣這樣只相隔了150海里,使用着同一種語言的兄弟地區,也分別採用了不一樣的 DBCS 編碼方案——當時的中國人想讓電腦顯示漢字,就必須裝上一個」漢字系統」,專門用來處理漢字的顯示、輸入的問題,像是那個臺灣的愚昧封建人士寫的算命程序就必須加裝另外一套支持 BIG5 編碼的什麼」倚天漢字系統」才能夠用,裝錯了字符系統,顯示就會亂了套!這怎麼辦?並且世界民族之林中還有那些一時用不上電腦的窮苦人民,他們的文字又怎麼辦? 真是計算機的巴比倫塔命題啊!

正在這時,大天使加百列及時出現了——一個叫 ISO(國際標誰化組織)的國際組織決定着手解決這個問題。他們採用的方法很簡單:廢了全部的地區性編碼方案,從新搞一個包括了地球上全部文化、全部字母和符號 的編碼!他們打算叫它」Universal Multiple-Octet Coded Character Set」,簡稱 UCS, 俗稱 「unicode「。

unicode開始制訂時,計算機的存儲器容量極大地發展了,空間不再成爲問題了。因而 ISO 就直接規定必須用兩個字節,也就是16位來統一表示全部的字符,對於ASCII裏的那些「半角」字符,unicode包持其原編碼不變,只是將其長度由原來的8位擴展爲16位,而其餘文化和語言的字符則所有從新統一編碼。因爲」半角」英文符號只須要用到低8位,因此其高8位永遠是0,所以這種大氣的方案在保存英文文本時會多浪費一倍的空間。

這時候,從舊社會裏走過來的程序員開始發現一個奇怪的現象:他們的 strlen 函數靠不住了,一個漢字再也不是至關於兩個字符了,而是一個!是的,從unicode開始,不管是半角的英文字母,仍是全角的漢字,它們都是統一的」一個字符「!同時,也都是統一的」兩個字節「,請注意」字符」和」字節」兩個術語的不一樣,「字節」是一個8位的物理存貯單元,而「字符」則是一個文化相關的符號。在unicode中,一個字符就是兩個字節。一個漢字算兩個英文字符的時代已經快過去了。

unicode一樣也不完美,這裏就有兩個的問題,一個是,如何才能區別unicode和ascii?計算機怎麼知道三個字節表示一個符號,而不是分別表示三個符號呢?第二個問題是,咱們已經知道,英文字母只用一個字節表示就夠了,若是unicode統一規定,每一個符號用三個或四個字節表示,那麼每一個英文字母前都必然有二到三個字節是0,這對於存儲空間來講是極大的浪費,文本文件的大小會所以大出二三倍,這是難以接受的

unicode在很長一段時間內沒法推廣,直到互聯網的出現,爲解決unicode如何在網絡上傳輸的問題,因而面向傳輸的衆多 UTF(UCS Transfer Format)標準出現了,顧名思義,UTF-8就是每次8個位傳輸數據,而UTF-16就是每次16個位。UTF-8就是在互聯網上使用最廣的一種unicode的實現方式,這是爲傳輸而設計的編碼,並使編碼無國界,這樣就能夠顯示全世界上全部文化的字符了。UTF-8最大的一個特色,就是它是一種變長的編碼方式。它可使用1~4個字節表示一個符號,根據不一樣的符號而變化字節長度,當字符在ASCII碼的範圍時,就用一個字節表示,保留了ASCII字符一個字節的編碼作爲它的一部分,注意的是unicode一箇中文字符佔2個字節,而UTF-8一箇中文字符佔3個字節)。從unicode到utf-8並非直接的對應,而是要過一些算法和規則來轉換。

Unicode符號範圍 UTF-8編碼方式
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

最後簡單總結一下:

中國人民經過對 ASCII 編碼的中文擴充改造,產生了 GB2312 編碼,能夠表示6000多個經常使用漢字。

漢字實在是太多了,包括繁體和各類字符,因而產生了 GBK 編碼,它包括了 GB2312 中的編碼,同時擴充了不少。

中國是個多民族國家,各個民族幾乎都有本身獨立的語言系統,爲了表示那些字符,繼續把 GBK 編碼擴充爲 GB18030 編碼。

每一個國家都像中國同樣,把本身的語言編碼,因而出現了各類各樣的編碼,若是你不安裝相應的編碼,就沒法解釋相應編碼想表達的內容。

終於,有個叫 ISO 的組織看不下去了。他們一塊兒創造了一種編碼 UNICODE ,這種編碼很是大,大到能夠容納世界上任何一個文字和標誌。因此只要電腦上有 UNICODE 這種編碼系統,不管是全球哪一種文字,只須要保存文件的時候,保存成 UNICODE 編碼就能夠被其餘電腦正常解釋。

UNICODE 在網絡傳輸中,出現了兩個標準 UTF-8 和 UTF-16,分別每次傳輸 8個位和 16個位。因而就會有人產生疑問,UTF-8 既然能保存那麼多文字、符號,爲何國內還有這麼多使用 GBK 等編碼的人?由於 UTF-8 等編碼體積比較大,佔電腦空間比較多,若是面向的使用人羣絕大部分都是中國人,用 GBK 等編碼也能夠。

3、字符編碼詳解

1.爲什麼須要編碼

咱們知道,全部的信息最終都表示爲一個二進制的字符串每個二進制位(bit)有0和1兩種狀態。當咱們須要把字符'A'存入計算機時,應該對應哪一種狀態呢,存儲時,咱們能夠將字符'A'用01000010(這個隨便編的)二進制字符串表示,存入計算機;讀取時,再將01000010還原成字符'A'。那麼問題來了,存儲時,字符'A'應該對應哪一串二進制數呢,是01000010?或者是10000000 11110101?說白了,就是須要一個規則。這個規則能夠將字符映射到惟一一種狀態(二進制字符串),這就是編碼。而最先出現的編碼規則就是ASCII編碼,在ASCII編碼規則中,字符'A'既不對應01000010,也不對應1000 0000 11110101,而是對應**01000001*(不要問爲何,這是規則)。

2.ASCII

這套編碼規則是由美國定製,一共規定了128個字符的編碼,好比空格"SPACE"是32(十進制)(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括 32個不能打印出來的控制符號),只佔用了一個字節(8 bit)的後面7位,最前面的1位統一規定爲0總共纔有128個字符編碼,一個字節都沒有用完,這好像彷佛有點太少了。因而乎,就開始壓榨最高位,對其爲1時也進行編碼,利用最高位進行編碼的方式就稱爲非ASCII編碼,如ISO-8859-1編碼。

3.ISO-8859-1

這套編碼規則由ISO組織制定。是在 ASCII 碼基礎上又制定了一些標準用來擴展ASCII編碼,即 00000000(0) ~ 01111111(127) 與ASCII的編碼同樣,對 10000000(128) ~ 11111111(255)這一段進行了編碼,如將字符**§編碼成 10100111(167)。ISO-8859-1編碼也是單字節編碼,最多可以表示256**個字符。Latin1是ISO-8859-1的別名,有些環境下寫做Latin-1。可是,即便可以表示256個字符,對中文而言,仍是太少了,一個字節確定不夠,必須用多個字節表示。可是,因爲是單字節編碼,和計算機最基礎的表示單位一致,因此不少時候,仍舊使用 ISO8859-1編碼來表示。並且在不少協議上,默認使用該編碼。好比,雖然"中文"兩個字不存在ISO8859-1編碼,以GB2312編碼爲例,應該是D6D0 CEC4兩個字符,使用ISO8859-1編碼的時候則將它拆開爲4個字節來表示:D6D0 CEC4(事實上,在進行存儲的時候,也是以字節爲單位進行處理)。而若是是UTF編碼,則是6個字節e4 b8 ad e6 96 87。很明顯,這種表示方法還須要以另外一種編碼爲基礎才能正確顯示。而常見的中文編碼方式有GB23十二、BIG五、GBK。

4.GB2312

GB2312其對所收錄字符進行了"分區"處理,共94個區,區從1(十進制)開始,一直到94(十進制),每區含有94個位,位從1(十進制)開始,一直到94(十進制),共8836(94 * 94)個碼位,這種表示方式也稱爲區位碼,GB2312是雙字節編碼,其中高字節表示區,低字節表示位。各區具體說明以下:

01-09區收錄除漢字外的682個字符,有164個空位(9 * 94 - 682)。
10-15區爲空白區,沒有使用。
16-55區收錄3755個一級漢字(簡體),按拼音排序。
56-87區收錄3008個二級漢字(簡體),按部首/筆畫排序。
88-94區爲空白區,沒有使用。
複製代碼

那麼根據區位碼如何算出GBK2312編碼呢?區位碼的表示範圍爲0101 - 9494(包含了空的區位碼)。點擊這裏,查看中GB2312編碼區位碼。以後只須要按照以下規則進行轉化便可。

  1. 將區(十進制)轉化爲十六進制。
  2. 將轉化的十六進制加上A0,獲得GB2312編碼的高字節。
  3. 將位(十進制)轉化爲十六進制。
  4. 將轉化的十六進制加上A0,獲得GB2312編碼的低字節。
  5. 組合區和位,區在高字節,位在低字節。
  6. 獲得GB2312編碼。

例如:'李'字的區位碼爲3278(表示在32區,78位)。

  1. 將32(區)轉化爲十六進制爲20。
  2. 加上A0爲C0。
  3. 將78(位)轉化爲十六進制爲4E。
  4. 加上A0爲EE。
  5. 組合區和位,爲C0EE。
  6. 獲得GB2312編碼,即'李'字的GB2312編碼爲C0EE。

GB2312用兩個字節編碼,採用分區編碼,總共編碼的中文個數爲6763(3755 + 3008)。這些漢字只是最經常使用的漢字,已經覆蓋中國大陸99.75%的使用頻率。可是,還有一些漢字在GB2312中沒有被編碼,如'鎔'字,在GB2312中就沒有被編碼,這樣就致使了問題,隨之就出現了主流的GBK編碼。在講解GBK編碼以前,咱們另外講解一下BIG5編碼。

5.BIG5

BIG5採用雙字節編碼,使用兩個字節來表示一個字符。高位字節使用了0x81-0xFE,低位字節使用了0x40-0x7E,及0xA1-0xFE。該編碼是繁體中文字符集編碼標準,共收錄13060箇中文字,其中有二字爲重複編碼,即「兀、兀」(A461及C94A)和「嗀、嗀」(DCD1及DDFC)。具體的分區以下:  

8140-A0FE 保留給使用者自定義字符(造字區)
A140-A3BF 標點符號、希臘字母及特殊符號。其中在A259-A261,收錄了度量衡單位用字:兙兛兞兝兡兣嗧瓩糎。
A3C0-A3FE 保留。此區沒有開放做造字區用。
A440-C67E 經常使用漢字,先按筆劃再按部首排序。
C6A1-F9DC 其它漢字。
F9DD-F9FE 製表符。
複製代碼

點擊這裏,查看BIG5編碼。注意,BIG5編碼與GBK編碼沒有什麼關係。

6.GBK

GBK編碼擴展了GB2312,徹底兼容GB2312編碼(如'李'字的GBK、GB2312編碼均爲C0EE),但其不兼容BIG5編碼('長'字的BIG5編碼爲AAF8,GBK編碼爲E94C,'李'字的BIG5編碼爲A7F5 不等於C0EE),即若是使用GB2312編碼,使用GBK解碼是徹底正常的,可是若是使用BIG5編碼,使用GBK解碼,會出現亂碼。相比於GB2312編碼,GBK編碼了更多漢字,如'鎔'字。GBK編碼依然採用雙字節編碼方案,其編碼範圍:8140-FEFE,剔除xx7F碼位,共23940個碼位。能表示 21003 個漢字。點擊這裏,查看GBK編碼。點擊這裏,能夠查詢中文的其餘編碼。在GBK以後又出現了GB18030編碼,可是沒有造成主流,故不作介紹,至此,中文編碼的問題已經講解完成。那麼問題又來了,大陸網民與在海峽兩岸網民交流時,若都使用GBK編碼,則沒有問題,若一方使用GBK編碼,一方使用BIG5編碼,那麼就會出現亂碼問題,這是在海峽兩岸網民交流,若是漂洋過海進行交流呢?那就更容易出現亂碼問題,這時候咱們可能想,要是有一套全世界都通用的編碼就行了,不要擔憂,這樣的編碼確實是存在的,那就是Unicode。

7.Unicode

有兩個獨立的, 創立單一字符集的嘗試. 一個是國際標準化組織(ISO)的 ISO 10646 項目, 另外一個是由多語言軟件製造商組成的協會組織的 Unicode 項目. 在1991年先後, 兩個項目的參與者都認識到, 世界不須要兩個不一樣的單一字符集. 它們合併雙方的工做成果, 併爲創立一個單一編碼表而協同工做. 兩個項目仍都存在並獨立地公佈各自的標準, 但 Unicode 協會和 ISO/IEC JTC1/SC2 都贊成保持 Unicode 和 ISO 10646 標準的碼錶兼容, 並緊密地共同調整任何將來的擴展。

Unicode是指一張表,裏面包含了可能出現的全部字符,每一個字符對應一個數字,這個數字稱爲碼點(Code Point),如字符'H'的碼點爲72(十進制),字符'李'的碼點爲26446(十進制)。Unicode表包含了1114112個碼點,即從000000(十六進制) - 10FFFF(十六進制)。地球上全部字符均可以在Unicode表中找到對應的惟一碼點。點擊這裏,查詢字符對應的碼點。Unicode將碼空間劃分爲17個平面,從00 - 10(十六進制,最高兩位),即從0 - 16(十進制),每一個平面有65536個碼點(2^16),其中最重要的是第一個Unicode平面(碼位從0000 - FFFF),包含了最經常使用的字符,該平面被稱爲基本多語言平面(Basic Multilingual Plane),縮寫爲BMP,其餘平面稱爲輔助平面(Supplementary Planes),在基本多文種平面內, 從D800到DFFF之間的碼位區段是永久保留不映射到字符的, 所以UTF-16編碼巧妙的利用了這保留下來的碼位來對輔助平面內的字符進行編碼,這點後面進行講解。Unicode只是一個符號集,只規定的字符所對應的碼點,並無指定如何存儲,如何進行存儲出現了不一樣的編碼方案,關於Unicode編碼方案主要有兩條主線:UCS和UTF。UTF主線由Unicode Consortium進行維護管理,UCS主線由ISO/IEC進行維護管理。

8.UCS

UCS全稱爲"Universal Character Set",在UCS中主要有UCS-2和UCS-4。

1.UCS-2

UCS-2是定長字節的,固定使用2個字節進行編碼,從0000(十六進制)- FFFF(十六進制)的碼位範圍,對應第一個Unicode平面。採用BOM(Byte Order Mark)機制,該機制做用以下:

  1. 肯定字節流採用的是大端序仍是小端序。
  2. 肯定字節流的Unicode編碼方案。

2.UCS-4

UCS-4是定長字節的,固定使用4個字節進行編碼。也採用了BOM機制。

9.UTF

UTF全稱爲"Unicode Transformation Format",在UTF中主要有UTF-8,UTF-16和UTF-32。

1.UTF-8

UTF-8是一種變長編碼方式,使用1-4個字節進行編碼。UTF-8徹底兼容ASCII,對於ASCII中的字符,UTF-8採用的編碼值跟ASCII徹底一致。UTF-8是Unicode一種具體的編碼實現。UTF-8是在互聯網上使用最廣的一種Unicode的編碼規則,由於這種編碼有利於節約網絡流量(由於變長編碼,而非統一長度編碼)。關於Unicode碼點如何轉化爲UTF-8編碼,能夠參照以下規則:

  • 對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。所以對於英語字母,UTF-8編碼和ASCII碼是相同的。
  • 對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一概設爲10。剩下的沒有說起的二進制位,所有爲這個符號的unicode碼。
Unicode符號範圍 UTF-8編碼方式
(十六進制) (十進制) (二進制
0000 0000-0000 007F (0-127) 0xxxxxxx
0000 0080-0000 07FF (128-2047) 110xxxxx 10xxxxxx
0000 0800-0000 FFFF (2048-65535) 1110xxxx 10xxxxxx 10xxxxxx
001 0000-0010 FFFF (65536-1114111) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

說明:字符'A'的Unicode碼點爲65(十進制),根據上表,在第一行範圍,則字符'A'的UTF-8編碼爲01000001,中文字符'李'的Unicode碼點爲26446(十進制),二進制爲01100111 01001110,十六進制爲674E。根據上表,在第三行範圍,則將'李'二進制代碼從低位到高位依次填入x中,不足的填入0。獲得UTF-8編碼爲11100110 10011101 10001110,即E69D8E(十六進制)。

由上述編碼規則可知,0000 0000 - 0000 FFFF(第一行到第三行)爲Unicode第一個平面(基本多語言平面),而0001 0000 - 10 FFFF(第四行)爲Unicode其餘平面(輔助平面)。在基本多語言平面對應了絕大多數經常使用的字符。對於大於65535(十進制)的碼點,即在輔助平面上的碼點,須要使用4個字節來進行UTF-8編碼。

2.UTF-16

UTF-8是不定長的編碼,使用一、二、三、4個字節編碼,而UTF-16則只使用2或4個字節編碼。UTF-16也是Unicode一種具體的編碼實現。關於Unicode如何轉化爲UTf-16編碼規則以下:

  1. 若Unicode碼點在第一平面(BPM)中,則使用2個字節進行編碼。
  2. 若Unicode碼點在其餘平面(輔助平面),則使用4個字節進行編碼。

關於輔助平面的碼點編碼更詳細解析以下:輔助平面碼點被編碼爲一對16比特(四個字節)長的碼元, 稱之爲代理對(surrogate pair), 第一部分稱爲高位代理(high surrogate)或前導代理(lead surrogates),碼位範圍爲:D800-DBFF. 第二部分稱爲低位代理(low surrogate)或後尾代理(trail surrogates), 碼位範圍爲:DC00-DFFF。注意,高位代理的碼位從D800到DBFF,而低位代理的碼位從DC00到DFFF,總共剛好爲D800-DFFF,這部分碼點在第一平面內是保留的,不映射到任何字符,因此UTF-16編碼巧妙的利用了這點來進行碼點在輔助平面內的4字節編碼。

說明:字符'A'的Unicode碼點爲65(十進制),十六進制表示爲41,在第一平面。根據規則,UTF-16採用2個字節進行編碼。那麼問題又來了,知道了採用兩個字節編碼,而且咱們也知道計算機是以字節爲單位進行存儲,這兩個字節應該表示爲00 41(十六進制)?或者是41 00(十六進制)呢?這就引出了一個問題,須要用到以前說起的BOM機制來解決。

表示爲00 41意味着採用了大端序(Big endian),而表示爲41 00意味着採用了小端序。那麼計算機如何知道存儲的字符信息採用了大端序仍是小端虛呢?這就須要加入一些控制信息,具體是採用大端序,則在文件前加入FE FF,採用小端序,則在文件前加入FF FE。這樣,當計算開始讀取時發現前兩個字節爲FE FF,就表示以後的信息採用的是小端序,反之,則是大端序。

字符 (沒法顯示,只能截圖顯示),其Unicode碼點爲65902(十進制),十六進制爲1016E,很顯然,已經超出了第一平面(BMP)所能表示的範圍。其在輔助平面內,根據規則,UTF-16採用4個字節進行編碼。然而其編碼不是簡單擴展爲4個字節(00 01 01 6E),而是採用以下規則進行計算。

  1. 使用Unicode碼位減去100000(十六進制),獲得的值擴展20位(由於Unicode最大爲10 FF FF(十六進制),減去1 00 00(十六進制)後,獲得的結果最大爲0FFF FF(十六進制),即爲20位,不足20位的,在高位加一個0,擴展至20位便可)。
  2. 將步驟一獲得的20位,按照高十位和低十位進行分割。
  3. 將步驟二的高十位擴展至2個字節,再加上D800(十六進制),獲得高位代理或前導代理。取值範圍是D800 - 0xDBFF。
  4. 將步驟二的低十位擴展至2個字節,再加上DC00(十六進制),獲得低位代理或後尾代理。取值範圍是DC00 - 0xDFFF。

Unicode轉UTF-16規則流程圖以下:

按照這個規則,咱們計算字符的UTF-16編碼,咱們知道其碼點爲1016E,減去10000獲得016E,擴展至0016E,進行分割,獲得高十位爲00 0000 0000,十六進制爲0000,加上D800爲D800;獲得低十位爲01 0110 1110,十六進制爲016E,加上DC00爲DD6E;綜合獲得D8 00 DD 6E。即UTF-16編碼爲D8 00 DD 6E(也可爲D8 0 DD 6E)。

而對於UTF-32是使用4個字節表示,也採用BOM機制,能夠類比UTF-16,這裏再也不額外介紹。

4、字符編碼區別

1. UCS-2 與 UTF-16區別

從上面的分析知道,UCS-2採用的兩個字節進行編碼。在0000到FFFF的碼位範圍內,它和UTF-16基本一致,爲何說基本一致,由於在UTF-16中從U+D800到U+DFFF的碼位不對應於任何字符,而在使用UCS-2的時代,U+D800到U+DFFF內的值被佔用。

UCS-2只能表示BMP內的碼點(只採用2個字節),而UTF-16能夠表示輔助平面內的碼點(採用4個字節)。

咱們能夠抽象的認爲UTF-16可當作是UCS-2的父集。在沒有輔助平面字符(surrogate code points)前,UTF-16與UCS-2所指的意思基本一致。但當引入輔助平面字符後,想要表示輔助平面字符時,就只能用UTF-16編碼了。

2.UCS -4與 UTF-16的區別

在BMP上,UTF-16採用2個字節表示,而在輔助平面上,UTF-16採用的是4個字節表示。對於UCS-4,無論在哪一個平面都採用的是四個字節表示。

3 爲何UTF-8編碼不須要BOM機制

由於在UTF-8編碼中,其自身已經帶了控制信息,如1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx,其中1110就起到了控制做用,因此不須要額外的BOM機制。

5、談談emoji

終於能夠說說emoji了。Emoji早先由日本企業發明,日文將其稱爲「絵文字」。後來隨着智能手機的推廣,全世界都在用,因而被Unicode收編了。截至Unicode 10.0,共有1144個emoji被收錄。

🍃與

這個小標題可能會有亂碼或者顯示一半的狀況,這是有意爲之。你們不妨來看一張圖:

圖中是同一段數據在我司內部通信工具和Mac版微信中的表現。

拋去格式不太友好以外,有一處特別的地方是emoji的兩片飄葉顯示不對。實際上除了微信,這個emoji在幾乎哪都不顯示。這就奇怪了,我特意把兩個軟件中的飄葉都複製出來,結果發現他們壓根連碼點都不一樣:

  • 🍃: U+1F343
  • : U+E447

emmmm...同一個emoji怎麼會變碼點呢?emoji的特殊規則?沒據說啊。。

幾經搜索,我終於找到了緣由。原來iPhone中使用的emoji最先是軟銀的一套編碼,飄葉在這套編碼中的碼點正是U+E447。這個碼點位於用戶可自定義的私有碼位區域,所以現今以Unicode的角度來看這個字符,它是沒有確切的字形的。如今絕大部分軟件也都再也不支持這套老emoji了。這個emoji來自於一位微信用戶的暱稱,剛好微信還支持這套老emoji,因而可以顯示出來。

從圖中能夠看到,飄葉在這套編碼裏剛好是U+E447。

單色與彩色

若是emoji就這點玩法的話,也不至於寫這麼一章了。實際上emoji比我最初的認識還要複雜不少。Unicode的emoji除了可使用你們平時見到的彩色來展現,還能夠用單色來展現,以適應一些很是簡單的顯示設備。

怎麼作呢?規則就是在普通的emoji碼點以後,緊跟一個用來表示顏色版本的「變幻符」,這個變幻符有兩個取值:VS15(U+FE0E)和VS16(U+FE0F)。其中VS15表示強制使用單色版,而VS16則表示強制使用彩色版。若是沒有變幻符呢,每一個emoji可使用本身默認的展現。

舉個例子來講,U+26A0這個emoji能夠有兩種樣子:

  • ⚠︎(U+26A0 + U+FE0E)
  • ⚠️(U+26A0 + U+FE0F)

各位還記得Unicode當中的「組合字」麼,殊途同歸。

膚色

想必你們已經見過了,如今每一個人類emoji都有不少種膚色版本。Unicode從8.0開始,爲全部展現人或人體部位的emoji都增長了膚色控制。

在普通人物的emoji碼點以後若是跟上一個膚色碼點,那麼這個emoji就會採用相應的膚色。舉個例子:

👃🏿 = 👃(U+1F443) + 🏿(U+1F3FF)

實際上無論emoji中的人是否露出皮膚,均可以這樣組合,畫emoji的人能夠去抉擇如何去表現。不管如何,如今用emoji已經能夠這麼幹了:

🎅🏿???

emoji裏的全家福

上面的emoji組合,都是同一個emoji內部的事情,然而實際上多個獨立的emoji也能夠進行組合。

在Unicode中,存在一個特殊的碼點,被稱爲零寬鏈接符(ZWJ),其碼點爲U+200D。這個零寬鏈接符在平時是不會顯示出來的,否則也不會叫零寬鏈接符了。以前網上有人貼出「空字符串」幾百個字節,就是用它了。

零寬鏈接符在emoji中的做用,就是能夠i將多個emoji組合成一個更大的emoji。你們平時在網上看到的全家福emoji,正是這樣作出來的:

👨‍❤️‍💋‍👨 = 👨(U+1F468) + ZWJ(U+200D) + ❤(U+2764) + ZWJ(U+200D) + VS16(U+FE0F) + ZWJ(U+200D) +💋(U+1F48B)+ ZWJ(U+200D) +👨(U+1F468)

能夠說是很解耦了。。

固然也不是全部的軟件如今都支持ZWJ這種玩法,對於不支持的軟件,全家福就會被打散。

Unicode && JavaScript

那麼做爲一個前端,JavaScript對Unicode的支持是怎樣的呢?事實是,JavaScript的字符串使用UTF-16來存儲字符。在我印象裏,有這麼幾個函數與Unicode關係最大:

String.prototype.charAt() //返回指定位置的字符 String.prototype.charCodeAt() //返回指定位置的UTF-16編碼 String.prototype.codePointAt() //返回指定位置的Unicode碼點 String.length //返回對象中字符串所佔的UTF-16單元數量

幹說沒用,來考慮一下下面這段代碼:

// U+1F4A9
const str = '💩';
console.log(str.charCodeAt(0));
console.log(str.charAt(0));
console.log(str.codePointAt(0));
複製代碼

這是一坨屎無誤,第一行log的狀況是:

console.log(str.charCodeAt(0));
// 55357
// 0xD83D
複製代碼

嗯?好像和碼點對不上?答案是這樣的

'💩' === '\u{1F4A9}'
'💩' === '\uD83D\uDCA9'
複製代碼

哦,原來這坨屎已經超出了UTF-16最初支持的2字節表示,所以須要使用代理對來表示,這樣一來就不是直接給出碼點了。

這樣的話,第二行log輸出也就很好理解了:

console.log(str.charAt(0));
// ‘?'
複製代碼

charAt比較簡單,只是單純地將UTF-16字符串按下標返回對應的字符,這裏會打出一個代理對的一半,因此顯示不出來。也就是說chartAt是不會完整吐出一個須要代理對的字來的。

第三個log給出了你可能最想要的:

console.log(str.codePointAt(0));
// 128169
// 0x1F4A9
複製代碼

這是真正的Unicode碼點無誤了。那麼console.log(str.length);的話,會輸出什麼呢?因爲這個屬性返回的是UTF-16單元的數量,而這坨屎須要2個UTF-16單元,所以其輸出會是2。這可不是字節數哦。

那麼,既然str.codePointAt(0)可以返回出整坨屎的Unicode碼點,那str.codePointAt(1)會返回什麼呢?ES標準說了,若是這個下標不是代理對的開頭,那麼只返回指向的UTF-16單元,也就是說:

console.log(str.codePointAt(1));
// 56489
// 0xDCA9
複製代碼

最後說一下for循環的區別:

  • for(...i++;...)...str[i]: 按charCodeAt()進行循環
  • for…in: 按charCodeAt()進行循環
  • for…of: 按codePointAt()進行循環

因此只有for...of是真正理解Unicode的。你們用for來循環的時候,可要當心了,不然一不當心就會把代理對給拆開。

細節是魔鬼

計算機的歷史只有短短不到100年的時間,而互聯網則只有不到30年。由於歷史很短,不少時候咱們會產生一種假象,那就是計算機的歷史好像是筆直的,一切設計都很合理、恰到好處,只需理解一下高抽象層次的概念和原理便可。而事實上則剛好相反,計算機世界的歷史崎嶇不平,充滿了錯誤和由於錯誤而顛簸的設計,這裏面隱藏了大量的細節。有時咱們僞裝本身已經對程序瞭如指掌,「啊,編碼嘛,不就是映射一下嘛;哦,HTTP協議嘛,很簡單啊,就是個抽象層而已啊」,僞裝本身是高級程序員,所以好像能夠忽略這些細節。實際呢,處處都是坑!

細節就是魔鬼,即便在看起來並不複雜的字符編碼上也是如此。若是你忽視了這些,就只有用戶來替你承擔了。

相關文章
相關標籤/搜索