簡介
在本文中你将了解到Unicode和UTF-8,UTF-16,UTF-32的關系,同時你還會了解變種UTF-8,并且探讨一下UTF-8和變種UTF-8在java中的應用。
一起來看看吧。
Unicode的發展史
在很久很久以前,西方世界出現了一種叫做計算機的高科技産品。
初代計算機隻能做些簡單的算數運算,還要使用人工打孔的程式才能運作,不過随着時間的推移,計算機的體積越來越小,計算能力越來越強,打孔已經不存在了,變成了人工編寫的計算機語言。
一切都在變化,唯有一件事情沒有變化。這件事件就是計算機和程式設計語言隻流傳在西方。而西方日常交流使用26個字母加有限的标點符号就夠了。
最初的計算機存儲可以是非常昂貴的,我們用一個位元組也就是8bit來存儲所有能夠用到的字元,除了最開始的1bit不用以外,總共有128中選擇,裝26個小寫+26個大寫字母和其他的一些标點符号之類的完全夠用了。
這就是最初的ASCII編碼,也叫做美國資訊交換标準代碼(American Standard Code for Information Interchange)。
後面計算機傳到了全球,人們才發現好像之前的ASCII編碼不夠用了,比如中文中常用的漢字就有4千多個,怎麼辦呢?
沒關系,将ASCII編碼本地化,叫做ANSI編碼。1個位元組不夠用就用2個位元組嘛,路是人走出來的,編碼也是為人來服務的。于是産生了各種如GB2312, BIG5, JIS等各自的編碼标準。這些編碼雖然與ASCII編碼相容,但是互相之間卻并不相容。
這嚴重的影響了國際化的程序,這樣還怎麼去實作同一個地球,同一片家園的夢想?
于是國際組織出手了,制定了UNICODE字元集,為所有語言的所有字元都定義了一個唯一的編碼,unicode的字元集是從U+0000到U+10FFFF這麼多個編碼。
那麼unicode和UTF-8,UTF-16,UTF-32有什麼關系呢?
unicode字元集最後是要存儲到檔案或者記憶體裡面的,直接存儲的話,空間占用太大。那怎麼存呢?使用固定的1個位元組,2個位元組還是用變長的位元組呢?于是我們根據編碼方式的不同,分成了UTF-8,UTF-16,UTF-32等多種編碼方式。
其中UTF-8是一種變長的編碼方案,它使用1-4個位元組來存儲。UTF-16使用2個或者4個位元組來存儲,JDK9之後的String的底層編碼方式變成了兩種:LATIN1和UTF16。
而UTF-32是使用4個位元組來存儲。這三種編碼方式中,隻有UTF-8是相容ASCII的,這也是為什麼國際上UTF-8編碼方式比較通用的原因(畢竟計算機技術都是西方人搞出來的)。
Unicode詳解
知道了Unicode的發展史之後,接下來我們詳解講解一下Unicode到底是怎麼編碼的。
Unicode标準從1991年釋出1.0版本,已經發展到2020年3月最新的13.0版本。
Unicode能夠表示的字元串範圍是0到10FFFF,表示為U+0000到U+10FFFF。
其中U+D800到U+DFFF的這些字元是預留給UTF-16使用的,是以Unicode的實際表示字元個數是216 − 211 + 220 = 1,112,064個。
我們将Unicode的這些字元集分成17個平面,各個平面的分布圖如下:

以Plan 0為例,Basic Multilingual Plane (BMP)基本上包含了大部分常用的字元,下圖展示了BMP中所表示的對應字元:
上面我們提到了U+D800到U+DFFF是UTF-16的保留字元。其中高位U+D800–U+DBFF和低位U+DC00–U+DFFF是作為一對16bits來對非BMP的字元進行UTF-16編碼。單獨的一個16bits是無意義的。
UTF-8
UTF-8是用1到4個位元組來表示所有的1,112,064個Unicode字元。是以UTF-8是一種變長的編碼方式。
UTF-8目前是Web中最常見的編碼方式,我們看下UTF-8怎麼對Unicode進行編碼:
最開始的1個位元組可以表示128個ASCII字元,是以UTF-8是和ASCII相容的。
接下來的1,920個字元需要兩個位元組進行編碼,涵蓋了幾乎所有拉丁字母字母表的其餘部分,以及希臘語,西裡爾字母,科普特語,亞美尼亞語,希伯來語,阿拉伯語,叙利亞語,Thaana和N'Ko字母,以及組合變音符号标記。BMP中的其餘部分中的字元需要三個位元組,其中幾乎包含了所有常用字元,包括大多數中文,日文和韓文字元。Unicode中其他平面中的字元需要四個位元組,其中包括不太常見的CJK字元,各種曆史腳本,數學符号和表情符号(象形符号)。
下面是一個具體的UTF-8編碼的例子:
UTF-16
UTF-16也是一種變長的編碼方式,UTF-16使用的是1個到2個16bits來表示相應的字元。
UTF-16主要在Microsoft Windows, Java 和 JavaScript/ECMAScript内部使用。
不過UTF-16在web上的使用率并不高。
接下來,我們看一下UTF-16到底是怎麼進行編碼的。
首先:U+0000 to U+D7FF 和 U+E000 to U+FFFF,這個範圍的字元,直接是用1個16bits來表示的,非常的直覺。
接着是:U+010000 to U+10FFFF
這個範圍的字元,首先減去0x10000,變成20bits表示的0x00000–0xFFFFF。
然後高10bits位的0x000–0x3FF加上0xD800,變成了0xD800–0xDBFF,使用1個16bits來表示。
低10bits的0x000–0x3FF加上0xDC00,變成了0xDC00–0xDFFF,使用1個16bits來表示。
U' = yyyyyyyyyyxxxxxxxxxx // U - 0x10000
W1 = 110110yyyyyyyyyy // 0xD800 + yyyyyyyyyy
W2 = 110111xxxxxxxxxx // 0xDC00 + xxxxxxxxxx
這也是為什麼在Unicode中0xD800–0xDFFF是UTF-16保留字元的原因。
下面是一個UTF-16編碼的例子:
UTF-32
UTF-32是固定長度的編碼,每一個字元都需要使用1個32bits來表示。
因為是32bits,是以UTF-32可以直接用來表示Unicode字元,缺點就是UTF-32占用的空間太大,是以一般來說很少有系統使用UTF-32.
Null-terminated string 和變種UTF-8
在C語言中,一個string是以null character ('0')NUL結束的。
是以在這種字元中,0x00是不能存儲在String中間的。那麼如果我們真的想要存儲0x00該怎麼辦呢?
我們可以使用變種UTF-8編碼。
在變種UTF-8中,null character (U+0000) 是使用兩個位元組的:11000000 10000000 來表示的。
是以變種UTF-8可以表示所有的Unicode字元,包括null character U+0000。
通常來說,在java中,InputStreamReader 和 OutputStreamWriter 預設使用的是标準的UTF-8編碼,但是在對象序列化和DataInput,DataOutput,JNI和class檔案中的字元串常量都是使用的變種UTF-8來表示的。
本文已收錄于 http://www.flydean.com/java-string-encodings/最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!
歡迎關注我的公衆号:「程式那些事」,懂技術,更懂你!