字元集與字元編碼
嚴格來說,一個字元在計算機中的表示形式,與字元集、字元編碼兩方面相關。字元集定義一個字元對應的碼位,而字元編碼則定義碼位在計算機中如何由一個或幾個byte組合起來表示。
但在很多地方(如Java、MySQL),并不嚴格區分字元集與字元編碼。這其中一個原因,可能是大多數字元集隻對應了一種字元編碼,如US-ASCII、GBK編碼等。Unicode字元集比較特殊,對應了多種編碼方式,如UTF-8、UTF-16BE等。
也就是說,字元集和字元編碼之間,是一對一或一對多的關系。
在Java中,Charset類直接用來表示各種字元編碼的名稱(因為字元編碼肯定隻對應一個字元集),如Java中Charset的名稱包括UTF-8、UTF-16BE、UTF-32BE、US-ASCII、Big5、GB2312、GBK、GB18030等。而且一個Charset名稱可能會有多個别名,如 UTF-8 的别名有 unicode-1-1-utf-8、UTF8,UTF-16 的别名有 UTF_16、unicode、utf16、UnicodeBig。
Java虛拟機(JVM)的預設字元集
Java中有個系統屬性"file.encoding",從字面意思看就是制定檔案的預設編碼方式。該值依賴于所在地區與底層作業系統的字元集。我在我的 Windows 10 的 JDK8 上測試得到的"file.encoding"預設為UTF-16BE。
Java類庫中的Charset類有個靜态方法defaultCharset(),用于擷取JVM的預設字元集。該方法在JDK8中的實作邏輯如下:
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = AccessController.doPrivileged(
new GetPropertyAction("file.encoding"));
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}
return defaultCharset;
}
從代碼中可以看出:
- 如果指定了"file.encoding"系統屬性,則JVM預設字元集就是"file.encoding"的指定值。
- 如果沒有指定"file.encoding"系統屬性,則JVM預設字元集是"UTF-8"。
- 該方法隻會在第一次調用時通過"file.encoding"初始化預設字元集。後續如果改了"file.encoding",defaultCharset()的傳回值不會改變。
Java中涉及編碼的地方主要由3個(URL編碼和Base64編碼除外):String對象相關的編碼、Java工程中檔案存儲的編碼,以及ByteBuffer相關的編碼。
String對象相關的編碼
首先要明白,Java 中的字元char、字元數組char[],在記憶體中儲存的都是字元在Unicode字元集中的碼位(Code Point,或稱碼點),是一個兩位元組的整數。
通過檢視String的源碼,我們不難發現String的底層表示是char[]。即我們通過某種方法,在将一段文本S(如"時間的朋友")轉換成String對象以後,String對象就會儲存下S中的每個字元對應的Unicode碼,将這些Unicode碼組成一個char[]。
String對象中與編碼相關的方法有兩類,一類是String傳入byte[]的構造方法new String(),另一類是getBytes()方法。
new String()在以byte[]構造字元串時,可以指定字元集,如果未指定則用JVM預設字元集對位元組數組進行解碼,進而得到一個char[]數組。
getBytes()也可以指定字元集,如果不指定,則與new String()方法一樣,根據JVM預設的字元集進行編碼。
檔案存儲的編碼
寫入檔案的是一個個byte,是以需要對一個個位元組進行編碼,将其表示成若幹個byte寫入檔案中。
在Java的IO庫中,有流(OutputStream、InputStream)和XX器(Writer、Reader)兩類。前者流是直接操作byte的,輸入輸出都是byte,而Writer、Reader的内部處理則涉及char與byte的轉換,是以需要指定字元集。
抽象類 Writer 有子類 OutputStreamWriter,在構造它的對象時可以指定一個Charset。如果未指定,則預設會采用JVM的預設字元集(也就是"file.encoding"),來進行char到byte的轉換,進而寫入檔案中。
而 OutputStreamWriter 有子類 FileWriter,其構造方法沒有指定Charset的參數,它是采用父類OutputStreamWriter的預設字元集(即"file.encoding")來進行寫檔案的。
抽象類 Reader 與 Writer 類似,其子類 InputStreamReader 的構造器也可以指定Charset,如果未指定則采用JVM的預設字元集(也就是"file.encoding"),來進行byte到char的轉換,将資料從檔案讀入記憶體中。
InputStreamReader 的子類 FileReader 也類似,構造方法沒有指定Charset的參數,采用的父類InputStreamReader的預設字元集(即"file.encoding")來進行讀檔案的。
ByteBuffer相關的編碼
在調用 ByteBuffer 的asCharBuffer()方法時,ByteBuffer 預設按UTF-16BE的編碼方式解析其内部的byte數組。
當然,是BIG_ENDIAN(即高位位元組放在前面,預設) 還是 LITTLE_ENDIAN,可以通過ByteBuffer的order(ByteOrder.LITTLE_ENDIAN)進行設定。