讓咱們從一個故事開始提及。話說北大是頗有哲學傳統的,當你準備踏進北大校門時,連門衛都會連問你三個終極哲學問題:java
你是誰?你從哪裏來?你要到哪裏去?數組
那麼這與咱們的問題又有何關係呢?我以爲理解內存中的編碼的關鍵在於理解String類型,所以咱們也來探討一下String的前世此生:String是誰(什麼)?String從哪裏來?String到哪裏去?編碼
當咱們可以清晰地回答這三個終極問題時,對文本在內存中的編碼也算理解得差很少了。spa
注:文中將用Java平臺爲例來探討這些問題。.net
要回答這個問題,源碼固然是最好的參考。代理
若是看String類型的聲明:code
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; // ... }
能夠看到它實現了所謂的CharSequence接口,因此它是一個char序列,內部實質是一個char數組。blog
也即上述代碼中的」char value[]「,(也許你以爲」char[] value「的寫法更習慣一些,二者是等價的)接口
若是再看String的length方法,事實就更清楚了,實際上取的是char數組的長度:內存
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; // ... public int length() { return value.length; } }
到如今爲止,能夠這樣看String:
如今新的問題是:什麼是char呢?Java中的char是一種基礎的數據類型,用於表示字符,長度爲16位。
能夠把char看做是無符號的16位短整型。
一個byte是8位,那麼一個char就至關於兩個byte了,因此也能夠把String視做爲byte數組。(這是毫無疑問的,事實上整個內存就是一個大大的byte數組)
那麼一個String在內存中老是佔據偶數個字節,具體地說是佔用length()×2個字節。
固然,單獨地拿出String中的一個byte出來是沒有意義的,你老是須要兩個兩個一塊兒地操做它們,但這並不妨礙咱們把它當作byte數組,它能夠說是有點特殊的byte數組。
顯然,因爲char是16位固定長度,它的容量老是有限的,上限是216=65536,能表示0-65535(0x0000-0xFFFF)。
即使滿打滿算它也只能表示6萬多個不一樣字符而已,另外一方面,Unicode規劃的字符空間高達100萬以上,最新版本已經定義的字符也超過了10萬。
規劃的碼點範圍具體爲U+0000-U+10FFFF。char能表示前面的U+0000-U+FFFF,對於U+10000-U+10FFFF則無能爲力。
因此一個顯然的事實就是單個的char沒法表示全部的字符。好比下面的這個音樂符:
它的十進制的碼點爲119070,已經遠超過65535,確定不能放到char裏,實驗也可證明這一點:
蘿蔔太多,坑位不夠,怎麼辦呢?
一種方式天然是對char進行擴展,好比弄成32位的,不過這樣會形成很大的內存浪費。
另外一種方式就是對後面的那些字符使用兩個char來表示,也便是所謂的代理對方式,須要注意的是不能跟單個char表示的字符衝突。
Java採用的就是這樣方式,其後果是使得char沒法與」抽象的字符「這一律念劃上等號,一一對應的關係被打破了。
咱們具體來看下是怎麼作的。
首先char有256×256=65536個空間:
經常使用的字符均可以在這個空間內表示,包括絕大多數的漢字。
好比「a」分配到的編碼是「0061」,而「你」分配到的是「4F60」。
那麼一個字符串,好比「a你」就有兩個char,內存中佔4個字節:
而後對於那個音樂符而言,它的碼點爲U+1D11E,有5位,固然不能簡單直接地分紅0001和D11E兩部分。
這樣會與U+0001和U+D11E衝突。
因此首先要保留一些char,它們單個而言不表明任何抽象的字符,具體地說保留了D800-DFFF共2048個位置:
而後橫豎弄成一張表,可以造成100多萬種組合(1K=1024):
在這種表示方式下,U+1D11E對應的是D834和DD1E兩個char:
具體的轉換方式可見:字符集與編碼(四)——Unicode
咱們就用這兩個char一塊兒來表示這個字符。這個字符沒法放到單個的char中,但它能夠放到String中,由於String是char數組。
以上其實就是UTF-16的編碼方式。你常常能聽到這樣的說法,好比:Java平臺在內存中使用Unicode編碼。這其實說得很籠統,讓咱們把它說得更具體一些:
Java中的String類型在內存中使用UTF-16編碼。
String以char做爲它的構成單元,這樣一個16位的char也稱爲UTF-16編碼的一個代碼單元(code unit)。
一般,一個char對應一個抽象的字符,但也可能須要兩個char構成一個所謂的代理對才能表示一個抽象的字符。
因此這也致使了一些尷尬的狀況,對於一些抽象字符它的長度是2.
這與咱們的直覺不符,又以下面的狀況:
兩個抽象字符,內部爲3個char,因此長度是3,在內存中則佔據了6個字節。你可能不是很喜歡這樣的String類型,但事實就是這樣。
天然,你有不少的選擇。若是你本身去實現一個語言平臺,你固然也能夠選擇一個其它的編碼,好比UTF-8,甚至是UTF-32做爲String的內部編碼。
考慮到UTF-32用四字節表示一個字符,一般一個int類型也是4字節,那麼這種方式幾乎能夠認爲是用一個int數組來保存字符。
明白了String是什麼以後,在下一篇再繼續探討String從哪裏來的問題。咱們將深刻探討String的構造,字節流和字符流以及編碼間的轉換等問題。