Unicode、UTF-八、UTF-1六、MUTF-八、Java編碼掃盲

本文導讀

不少人常常分不清UTF-8編碼和UTF-16編碼,或常常會問"Unicode編碼和UTF-8編碼有什麼區別聯繫","Java的外碼內碼又是什麼東西",這篇文章主要作一個關於編碼知識的簡單掃盲,包括對一些常見概念混淆進行區分講解。java

基礎概念

咱們常常提到的關於編碼的概念能夠粗略劃分爲兩類:數組

  • 字符集:將一個字符映射爲某個惟一的數字(碼值),如字符A在ascii碼中映射爲65
  • 字符編碼:將字符集用程序(字節)表示的一套規則,能夠認爲字符編碼是字符集在計算機上的一種實現方式,如utf-8和utf-16都是unicode碼的實現方式。

Unicode字符集介紹

Unicode字符集一開始提出的時候,認爲碼值範圍爲0-65535(0-FFFF,這一段區域也被稱爲Basic Multilingual Plane, 簡稱BMP)就能夠表示全部的字符,但隨着時代發展,0-FFFF也不夠容納全部字符,所以Unicode劃出了一個代理區:D800-DFFF, Unicode標準規定U+D800 - U+DFFF的值不對應於任何字符。這也是爲何有些人說:有些字符須要用兩個Unicode字符去表示的緣由。函數

目前Unicode的編碼空間爲0-10FFFF,根據第一段落能夠得知,當某個字符的Unicode碼值落在0-FFFF時,則只用一個Unicode字符便可表示,不然就會用兩個。編碼

UTF-8編碼介紹

Utf-8全稱爲8-bit Unicode Transformation Format,是一種針對Unicode字符集的可變長編碼,不一樣的Unicode碼點會使用不一樣的字節數去存儲,如ascii碼(都小於128)則會使用1個字節去存儲,一些經常使用字符(如部分中文)會使用2~3個字節去存儲,這有一些優點,首先對於ascii碼徹底兼容,且對於某些場景(只存在ascii碼)編碼後佔用空間少,缺點也很明顯,當遇到的都是須要佔用3個字節存儲的Unicode碼點時,則會耗費更大的空間。代理

utf-8編碼的基本規則以下:code

Unicode碼範圍 Utf-8編碼格式
0x0000-0x007F(0~127) 0xxxxxxx
0x0080-0x07FF(128~2047) 110xxxxx 10xxxxxx
0x0800-0xFFFF(2048-65535) 1110xxxx 10xxxxxx 10xxxxxx
0x10000-0X10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

首先將Unicode碼用二進制表示,而後根據其所屬的Unicode範圍對應的Utf-8編碼格式截取最後幾位。orm

如0x0000-0x00FF對應的Utf-8編碼格式是0xxxxxxx,則截取最後7位(有效表示位其實也只有7位,由於從右往左第8位必定是0),對應填到x中。對象

同理,0x0080-0x07FF(0000 0000 1000 0000 — 0000 0111 1111 1111),則截取後11位(有效表示位其實只有11位,由於從右往左第12位必定是0),對應填到x中。utf-8

0x0080-0xFFFF(0000 1000 0000 0000 — 1111 1111 1111 1111),截取後15位(有效表示位其實只有16位,由於從右往左第17位必定是0),對應填到x中。ci

0x100000-0X10FFFF(0000 0001 0000 0000 0000 — 0001 0000 1111 1111 1111 1111),截取後21位(有效表示位其實只有21位,從右往左第22位必定是0),對應填到x中。

所以,咱們能夠獲得全部Unicode的Utf-8編碼規則。

UTF-16編碼介紹

在Unicode字符集中講到,Unicode字符集存在一個拓展區域:D800-DFFF,用於表示碼點在0x10000-0x10FFFF範圍的字符。當碰到某個在該範圍內的Utf-16字符,須要再讀一個Utf-16字符,將兩個Utf-16字符組合表示一個Unicode字符。

Unicode碼範圍 Utf-16編碼格式
0x0000-0xFFFF(0~65535) 使用2個字節存儲
0x10000-0x10FFFF(65536~) 使用4個字節存儲,須要利用上述提到的代理區

接下來將Unicode碼用二進制表示,嘗試將它用Utf-16編碼格式進行編碼。

對應0x0000-0xFFFF範圍的Unicode碼,直接將這16爲對應填入兩個字節(剛好16位)就能夠獲得Utf-16編碼。

而對應0x10000-0x10FFFF的Unicode碼,須要有一些特殊處理:

  1. 取後20位(減去10000),將這20位數字分爲高10位和低10位,高、低10位的範圍即爲0-0x3FF(00 0000 0000 — 11 1111 1111)
  2. 將高位加上0xD800,獲得值範圍爲0xD800—0xDBFF,將低位加上0xDC00,獲得值範圍爲0xDC00—0xDFFF;
  3. 將高位處理後的值(又稱前導代理)放在前2個字節中,將低位處理後的值(後導代理)放在後2個字節中。

經過處理,前導代理和後導代理剛好佔滿了0xD800—0xDFFF這一段代理區域,這樣處理的一個優勢在於,看到每個Utf-16編碼,能夠很清楚地肯定它是屬於前導代理、後導代理仍是除此之外的BMP區域中的Unicode。

MUTF-8編碼介紹

MUTF-8(Modified UTF-8)編碼,能夠認爲是對UTF-16編碼的再編碼。它的編碼方式與UTF-8編碼很是類似,只須要記住某些不一樣的狀況,其餘都與UTF-8編碼一致。

具體的不一樣狀況有二:

  1. 對於Unicode的0碼點,UTF-8直接使用1個字節去存儲(0000 0000),而MUTF-8會使用2個字節去存儲,最後存儲的值爲0xC080(1100 0000 1000 0000)。
  2. 對於0x10000-0x10FFFF這塊區域的Unicode碼,以前提過UTF-8是使用4個字節去存儲,而MUTF-8是對UTF-16的再編碼,因此MUTF-8是對UTF-16編碼的兩個字符分別用3個字節去編碼(由於這段區域的Unicode碼值轉爲UTF-16編碼後前導代理和後導代理的範圍是0xD800—0xDFFF,明顯大於0x0800),共須要6個字節

因此網上常常會提到UTF-8編碼,又提到用1—6個字節去編碼,其實說的是MUTF-8編碼。

Java的內碼與外碼

Java的內碼是UTF-16,外碼是MUTF-8。那什麼是內碼和外碼呢?

內碼:程序內部使用的字符編碼,如java的char,因此java的char是2字節16位;

外碼:程序外部交互時使用的字符編碼,如class文件。

在深刻理解Java虛擬機第三版6.3.2節中,咱們能夠得知其實Java的字符串常量(如String str="hello world")都是以CONSTANT_Utf8_info類型存在常量池中的,class文件的編碼是MUTF-8,因此CONSTANT_Utf8_info中存儲的根據不一樣的實現通常是存儲MUTF-8字節數組或UTF-16字符數組,每次構建時java.lang.String對象時,須要經過MUTF-8=>UTF-16的一個編碼轉換將外碼轉爲內碼,再將其塞到char數組(value)中。

在Java API層對字符串的操做,其實通常也是對UTF-16字符的操做,如charAt函數:

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

charAt函數實際是返回了一個char,因此是返回了一個UTF-16字符,它不必定是一個完整的Unicode碼點。

固然,在Java API層也可使用getBytes(」UTF-8")則是返回UTF-8編碼的字節數組。

總結

根據上面文章的講解,咱們就能夠講清楚下面幾個經常遇到的問題:

  • Unicode編碼和UTF-8編碼的區別? 其實Unicode只是字符集,而UTF-8是該字符集在計算機中的編碼表示。
  • 爲何說UTF-8是1~6個字節? 這裏的UTF-8其實在指MUTF-8編碼,MUTF-8使用1~3個字節對UTF-16編碼進行再編碼,因此就產生了使用6個字節表示一個Unicode字符的狀況。
  • Java的char到底佔用幾個字節?Java內碼使用的是UTF-16編碼,UTF-16對每一個Unicode字符使用2或4個字節進行編碼,因此對每一個char單位,實際上是佔用了2個字節。

另外,java虛擬機對字符串的表示或處理不少都是使用的UTF-16編碼或MUTF-8編碼,而UTF-8編碼通常是顯式經過Java API層的String.getBytes("UTF-8")函數獲得。

平時使用時,若是隻用Java語言開發通常不會有什麼亂碼問題,但若是本身想手動實現一個Java虛擬機,或是要經過JNI作一些事情的時候,就須要去了解一下Java的這些編碼知識了。如筆者以前參與的項目,用go處理Java虛擬機的編碼問題就很頭大,由於Go這邊默認使用的是UTF-8編碼,因此若是在實現常量池的過程當中用Go的string去存儲java.lang.String的實際內容,則可能出現一些奇怪的亂碼問題。

相關文章
相關標籤/搜索