1. 程式人生 > >一文解開java中字串編碼的小祕密

一文解開java中字串編碼的小祕密

[toc] # 簡介 在本文中你將瞭解到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個平面,各個平面的分佈圖如下: ![](https://img-blog.csdnimg.cn/2020072411164345.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_25,color_8F8F8F,t_70) 以Plan 0為例,Basic Multilingual Plane (BMP)基本上包含了大部分常用的字元,下圖展示了BMP中所表示的對應字元: ![](https://img-blog.csdnimg.cn/20200724120851662.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_25,color_8F8F8F,t_70) 上面我們提到了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進行編碼: ![](https://img-blog.csdnimg.cn/20200724121836690.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_25,color_8F8F8F,t_70) 最開始的1個位元組可以表示128個ASCII字元,所以UTF-8是和ASCII相容的。 接下來的1,920個字元需要兩個位元組進行編碼,涵蓋了幾乎所有拉丁字母字母表的其餘部分,以及希臘語,西裡爾字母,科普特語,亞美尼亞語,希伯來語,阿拉伯語,敘利亞語,Thaana和N'Ko字母,以及組合變音符號標記。BMP中的其餘部分中的字元需要三個位元組,其中幾乎包含了所有常用字元,包括大多數中文,日文和韓文字元。Unicode中其他平面中的字元需要四個位元組,其中包括不太常見的CJK字元,各種歷史指令碼,數學符號和表情符號(象形符號)。 下面是一個具體的UTF-8編碼的例子: ![](https://img-blog.csdnimg.cn/20200724122244833.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_25,color_8F8F8F,t_70) # 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來表示。 ~~~java U' = yyyyyyyyyyxxxxxxxxxx // U - 0x10000 W1 = 110110yyyyyyyyyy // 0xD800 + yyyyyyyyyy W2 = 110111xxxxxxxxxx // 0xDC00 + xxxxxxxxxx ~~~ 這也是為什麼在Unicode中0xD800–0xDFFF是UTF-16保留字元的原因。 下面是一個UTF-16編碼的例子: ![](https://img-blog.csdnimg.cn/20200724135429654.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_25,color_8F8F8F,t_70) # 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/](http://www.flydean.com/java-string-encodings/) > > 最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現! > > 歡迎關注我的公眾號:「程式那些事」,懂技術,更