好久以前入門學習 java 的時候第一次接觸到字符編碼這個東西,稍後在學習 web 基礎的時候接觸到了 UTF-八、字符亂碼。當時我覺得我已經足夠了解字符編碼了java
但直到我有一天我在掘金上看到一個問題:一箇中文字符佔幾個字節?
web
回想當初老師告訴咱們,一箇中文字符佔2個字節,可是這種說法其實大錯特錯,Unicode 編碼中一箇中文字符可不是佔2個字節的面試
因此纔有了今天這篇文章,不少東西咱們覺得已經足夠了解了,可是依然被面試官打趴下。歸根結底咱們爲何不清楚、不知道呢,就是由於咱們不是按照歷史的發展脈絡來學習的,因此咱們必然有遺落,有不清楚學習
國外的學習資料,不少都喜歡把相關歷史發展講的明明白白的。之前不甚理解,可是如今我理解了,不瞭解歷史,你就抓不住所有優化
時間從二戰來到50年代末60年代初,計算機發展很迅速,從軍用、科學向商業、辦公、教育等其餘領域擴展,這時爲了方便文字的顯示、儲存、輸入,字符編碼標準出現了ui
這直接崔生了世界首個字符編碼標準:ASCII
的誕生編碼
ASCII
是什麼東西,就是一組儲存一種語言全部字母和字符的 Map 集合。value 是該字母,key 是該字母統一對外調用的標記號碼,就像門牌地址同樣,讓咱們在一堆數據中準確快速的找到你。value 的集合叫:字符集
,key 的集合叫:編碼集
spa
字符集
會把你所在的語言體系裏面全部的字母、字符之類的全存進去,這些字符是計算機顯示的基礎,計算機根據咱們輸入的字符代號來找出這些字符自己,而後顯示出來3d
好比 value:A 對應的 key:1,咱們在輸入時,把1交給計算機,計算機就知道咱們想要顯示A這個字符code
計算機是 2 進制存儲的,每個 0 或 1 表示一位,8 個一位合起來是 1 個字節,計算機儲存是按字節爲基本單位存儲的
英文由於字符少,因此 7 位的範圍:0-128
就能涵蓋全部字符了,此時 編碼集
使用天然循序序號表示便可,7 位的 2 進制數,好比:0101011
可是在碰到中文、日本等文字後,這些文字不是字母拼接類型的語言,而是單個字符語言,中文裏有 3 萬個字符。字符集
卻是沒什麼,有什麼字符存什麼字符就好了。可是 編碼集
就有問題了,若是仍是使用天然順序序號來表示字符編號,那麼有可能一個字符的 2 進制編碼數會很長很長,很是不利於輸入和觀察,此時一箇中文詞語多是這樣的:0100100001000101010011000100110001001111
。這要是讓你輸入估計會是個災難,因此爲了解決 編碼集
過長的問題,你們決定讓 編碼集
在輸入時使用 16 進制,好比常見的:\u{1f44d}
,去掉格式化字符,1f44d 就是這個字符所在的編碼,這個 16 進制的編碼在內存中仍是以對應的 2 進制數儲存
還有一個問題,字符在 字符集
中是如何存儲的。像英文字符少,因此 7 位 2 進制 128 個位置 就能搞定,這樣英文的字符比編碼用一個字節就能夠了
可是中文呢,還有世界其餘的那些語言呢,文字內字符不少,尤爲是中文有幾萬個字符,那 編碼集
使用 7 位就不夠了,至少也得 16 位 65535 個位置才能放得下。這樣的話,一個字符就得用 2 個字節甚至更多字節表示了。可是中文中也會用到數字、應爲字母之類的,這些字符如果也用 2 個字節表示,就會浪費存儲空間,下降 CPU 計算效率
爲了應對這種狀況,有的 編碼集
採用可變字長,像英文字母之類的字符用 1 個字節,有的字符用 2 個字節或是更多。這種問題就叫作:存儲方式
優化
有的朋友會問爲何 2 個字節會有浪費存儲空間的問題呢?屏幕上雖然咱們看着是一個個文字,可是這些文字在計算機,也就是內存中全是按照字符對應的字符編碼的 2 進制數儲存的, 也就是 編碼集
這個東西,因此表示一個字符使用的字節越多,那麼越佔用,浪費資源
注意如下:
字符集、編碼集、存儲方式 這3者共同組成了一個字符編碼標準,他們其中有任何一個產生變化都會演變成一個新的字符編碼標準
有的字符編碼標準採用可變字長
字符編碼標準之間要兼容很難,不少文字亂碼就是字符標準之間不兼容的問題
但願我這種特例獨行的解釋能讓你們接受,我以爲這樣最好理解,以上沒有抄襲任何諸如百度百科之類的解釋,徹底是我本身的認知,有差錯請指出,在此萬分感謝!
1. ASCII 碼時代
1960年 ASCII 碼
字符編碼出臺,使用7位編碼,有效位置是 128 個,用來統一英文的輸入、儲存、顯示,由於計算機是按字節儲存的,因此補了一位,以 0 開頭
2. 擴展 ASCII 碼時代
ASCII 碼
出來後,效果很好,可是歐洲其餘國家有本身的語言,本身的字符,因此紛紛盯上了 ASCII 碼
沒有使用的補 0 的這一位,拓展成了有效空間爲 256 個的字符編碼。可是呢,這些歐洲國家本身搞本身的,搞出來的字符編碼相互不能通用,很是混亂,亂碼成了一個棘手的問題
3. GB2312/GBK 時代
1981年,我過出臺了本身的面向中文的字符編碼:GB2312
,包含 7445 個字符,包括 6763 個漢字,682 個字符
雖然又推出了:GBK
,支持更多的中文字符,支持共 21003 個漢字,而且完整支持中日韓文字
GB2312/GBK
系中文字符標準,window 中文版默認就是使用 GB2312
這個字符編碼,特色是每一個字符使用2個字節
4. Unicode 萬國碼
前面說過,你們本身搞本身的字符編碼,整個相互不通用,竟是亂碼,隨着互聯網的發展,這樣但是不行的,隨後 ISO 組織出面集合大夥搞了統一的,你們一塊兒使用的,兼容各自字符編碼的國際統一碼:Unicode
Unicode
使用4個字節(能夠擴容支持更多字節)的字符範圍,預設100多萬個字符位置,以容納世界上全部的語言,特殊字符,emoji 表情這些
Unicode
把目前分紅 17個扇區,每一個扇區有 65535 個位置,規定不一樣類型的字符存儲在不一樣的扇區
有一點十分重要,Unicode
只是一種 編碼集
規範,規定了一個字符對應的字符的位置,可是針對每一個字符都佔用4個字節的問題,又產生了 UTF
這種通過優化的 字符編碼規範
其餘的都不用詳說了,UTF 編碼
是咱們平時最經常使用的,須要詳細的展開一下,目前 UTF 編碼
有3種規範:
UTF-8:
可變字符編碼,佔用1到4個字節UTF-16:
可變字符編碼,佔用2到4個字節UTF-32:
不可變字符編碼,統一使用4個字節表示一個字符你們要知道這3實際上是一回事,搞清楚一個其餘也就明白了,都是優化字節佔用量。不少時候 Unicode
4個字節的儲存方式裏,這4個字節的數字裏面不少都是沒有用的,純粹爲了補位的,像英文1個字節就夠了,這就是優化的原動力
UTF-8
使用一至四個字節爲每一個字符編碼
使用一個字節編碼:
128 個 ASCII 字符(Unicode 範圍由 U+0000 至 U+007F)使用二個字節編碼:
帶有變音符號的拉丁文、希臘文、西裏爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文及馬爾代夫語(Unicode 範圍由 U+0080 至 U+07FF)使用三個字節編碼:
其餘基本多文種平面(BMP)中的字符(CJK屬於此類-Qieqie注),中文就在這個範圍內使用四個字節編碼:
其餘 Unicode 輔助平面的字符,好比 emoji 表情UTF-16
與 UTF-8
不一樣的地方在於英文等字符再也不是一個字符編碼了,而是2個
UTF-32
統一使用4個字節編碼,咱們處理 emoji 表情符號基本上都是轉成 UTF-32
來顯示
你們看懂了嗎~ 這就是 UTF-8
被普遍採用的緣由,對於英文的優化真是好...
有一道經典的面試題:中文佔幾個字符
,這下你們知道怎麼回答了吧,GBK 是2個,UTF-8 是3個,UTF-8 是4個
爲啥是3個呢?UTF 裏面每8位開頭都有表示分類和位置的佔位,3個字節裏面正好有1個字節被這種佔位佔走了,剩下的2位才能承載中文那幾萬個字符,因此 UTF 編碼中中文統一都是用3個字符編碼
你們看圖:
編碼 | 英文字節數 | 中文字節數 |
---|---|---|
GB2312 | 1 | 2 |
GBK | 1 | 2 |
GB18030 | 1 | 2 |
ISO-8859-1 | 1 | 1 |
UTF-8 | 1 | 3 |
UTF-16 | 2 | 4 |
UTF-32 | 4 | 4 |
UTF-16BE | 2 | 2 |
UTF-16LE | 2 | 2 |
讓我對字符編碼產生疑問的是從 emoji 顯示這個問題開始的,這裏記錄下我找到的資料:
不知作別的平臺怎麼讓 emoji 顯示出來的,反正 Flutter 想顯示 emoji 必須使用 UTF-32 這一種方式
Runes emojiString = new Runes('\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d} 哇哈哈哈哈!!!');
var index = String.fromCharCodes(emojiString)
複製代碼