本作品采用知識共享署名-非商業(yè)性使用-相同方式共享 2.5 中國大陸許可協(xié)議進行許可。
Java String是Java API中最常用的類,本文和大家談?wù)凷tring類的內(nèi)部原理,同時描述ISO-8859-1字符集在字符串處理中的獨特用處。
String類內(nèi)部管理著一個char類型的數(shù)組,Java API是這樣描述char基本類型的:
char 數(shù)據(jù)類型(和 Character 對象封裝的值)基于原始的 Unicode 規(guī)范,將字符定義為固定寬度的 16 位實體。
這一點我們可以通過下面的語句加以證實:
根據(jù)String類的構(gòu)造方法,我們可以這樣定義字符串:
上句代碼等效于:
同時,char基本類型與byte、short、int、long一樣可以用數(shù)值表示。所以上述代碼等效于:
需要注意的是,char基本類型始終使用16位即兩個字節(jié)表示一個字符,即使這個字符的Unicode值小于0xFF(如ASCII碼)。對于Unicode值大于0xFF的字符,如“中國”二字的Unicode編碼分別為/u4E2D和/u56FD,我們可以這樣創(chuàng)建:
當(dāng)然也可以這樣創(chuàng)建:
通過上面的描述可以明確兩個要點:
字符串對象中的每一個元素始終占據(jù)兩個字節(jié)長度,一個或兩個元素(增補字符占據(jù)兩個元素)表示一個字符。
字符串對象中的每一個元素都使用Unicode字符集進行編碼。
在出現(xiàn) Unicode 規(guī)范之前,計算機在處理字符串的問題上經(jīng)歷過ASCII和ANSI編碼兩個階段,在ASCII時代,計算機只能處理英文數(shù)字以及幾個基本符號,當(dāng)時使用的是單字節(jié)字符集(SBCS)。
各國為了能在計算機上處理本國的文字,制訂了相應(yīng)的國家標(biāo)準(zhǔn),規(guī)定了各自的ANSI編碼。如中文簡體使用GBK標(biāo)準(zhǔn);中文繁體使用BIG5標(biāo)準(zhǔn);日文使用Shift_JIS標(biāo)準(zhǔn)。在ANSI編碼時代,計算機使用多字節(jié)字符集(MBCS)處理文字。如“中國ABC”,在GB2312標(biāo)準(zhǔn)中,“中國”兩個字符分別使用兩個字節(jié)表示,而“ABC”三個英文字符又分別使用一個字節(jié)表示。各國文字的ANSI編碼互不通用,不能使用一種ANSI編碼表達(dá)多個國家的文字。
為了文字交流的順暢,也就是說為了達(dá)到在一個文本當(dāng)中既可以有中文簡體字存在,也可以有中文繁體字存在的目的。國際組織根據(jù)各國語言的特點,使用兩個字節(jié)的數(shù)據(jù)量將大部分國家的文字信息整合到一個字符集中,這就是Unicode編碼,也稱萬國碼。關(guān)于Unicode編碼的特點,前文已經(jīng)描述,那就是使用雙字節(jié)字符集(DBCS)處理文字。
在Java中,使用字節(jié)數(shù)組保存不同字符集的字符值,如使用ASCII字符集保存“abc”的方法如下:
使用GBK字符集保存“中國”二字的方法如下:
當(dāng)然我們也可以使用Unicode字符集保存“中國”二字。如:
一個特殊的字符集UTF-8是與Unicode規(guī)范對應(yīng)的多字節(jié)表示的字符集。如“中”字的UTF-8編碼為“0xE4, 0xB8, 0xAD”三個字節(jié)。
在這里,將這些與具體字符集相對應(yīng)的字節(jié)化的數(shù)據(jù)流稱為字節(jié)化字符數(shù)據(jù),與字符串對象形成鮮明對照的是,字符串對象的最小單位是兩個字節(jié)而字節(jié)化字符數(shù)據(jù)的最小單位則是一個字節(jié)。由此我們可以明確另外兩個要點:
字節(jié)化字符數(shù)據(jù)中的每一個元素始終占據(jù)一個字節(jié)長度,一個或多個元素表示一個字符。
字節(jié)化字符數(shù)據(jù)必須與一個字符集相對應(yīng)。
在Java程序運行過程中,字符串對象始終以Unicode編碼方式保存在內(nèi)存中,但將字符串對象保存到持久化資源(文件或數(shù)據(jù)庫)或?qū)⑵渫ㄟ^網(wǎng)絡(luò)傳輸時,通常是以字節(jié)化字符數(shù)據(jù)的方式進行處理。這樣就要求Java API必須提供兩者互換的功能。事實上這一功能在String類及Charset類中已經(jīng)提供。
一方面我們可以利用String類的getBytes()方法返回不同字符集的字節(jié)化字符數(shù)據(jù),其本質(zhì)是從Unicode字符集編碼向其它字符集編碼轉(zhuǎn)換的過程。例如:
上例的輸出結(jié)果為:
中國的UNICODE編碼:
0xFE 0xFF 0x4E 0x2D 0x56 0xFD
中國的GBK編碼:
0xD6 0xD0 0xB9 0xFA
中國的UTF-8編碼:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD
需要注意的是,從字符串對象中取出的Unicode編碼的字節(jié)化字符數(shù)據(jù)時,其開始部分存在一個BOM(ByteOrderMark),一般情況下,該BOM值為“0xFE 0xFF”,即大端字節(jié)序(BIG_ENDIAN)。如果BOM值為“0xFF 0xFE”則為小端字節(jié)序(LITTLE_ENDIAN)。
另一方面也可以利用String類的構(gòu)造方法根據(jù)不同字符集的字節(jié)化字符數(shù)據(jù)產(chǎn)生一個字符串對象,其本質(zhì)是從其它字符集編碼向Unicode字符集編碼轉(zhuǎn)換的過程。例如:
上例三個輸出語句均輸出“中國”二字。
上述兩種轉(zhuǎn)換過程,特別是Unicode字符集編碼向其它字符集編碼的轉(zhuǎn)換過程中會出現(xiàn)轉(zhuǎn)換失敗的現(xiàn)象。轉(zhuǎn)換失敗時該Unicode碼自動用0x3F代替。例如:
上例的輸出結(jié)果為:
中國的BIG5編碼:
0xA4 0xA4 0x3F
其中“國”由于沒有繁體中文BIG5字符集對應(yīng)的編碼值,所以會用0x3F表示。
ISO-8859-1是單字節(jié)字符集,是ASCII字符集的補充。通常情況下使用ISO-8859-1字符集進行字符串對象與字節(jié)化字符數(shù)據(jù)的互換操作與前述完全一致。例如:
上例的輸出結(jié)果為:
ISO-8859-1編碼:
0x61 0x62 0x63
UNICODE編碼:
0xFE 0xFF 0x00 0x61 0x00 0x62 0x00 0x63
通過此例可以看出,從ISO-8859-1字符集轉(zhuǎn)換成Unicode字符集的過程是將字節(jié)化字符數(shù)據(jù)中的每個一個byte類型元素直接保存成一個char類型元素。也就是說下面的代碼:
等效于:
需要注意的是,ISO-8859-1到Unicode的轉(zhuǎn)換過程是對編碼值為0x00 - 0xFF之間都有效的一種轉(zhuǎn)換。在ISO-8859-1字符集中,0x00-0x1F、0x7F、0x80-0x9F沒有定義。我們可以使用其中幾個無效編碼進行測試:
上例的輸出結(jié)果為:
ISO-8859-1編碼:
0x00 0x1A 0x7F 0x93
UNICODE編碼:
0xFE 0xFF 0x00 0x00 0x00 0x1A 0x00 0x7F 0x00 0x93
根據(jù)這一特點,我們可以總結(jié)出最后一個要點:
利用ISO-8859-1字符集,我們可以將任何一個字節(jié)數(shù)組無損保存到字符串對象中。
也就是說,可以利用這一特點將字節(jié)化字符數(shù)據(jù)的原始字節(jié)數(shù)據(jù)(而不是經(jīng)過Unicode字符集轉(zhuǎn)換之后的數(shù)據(jù))直接保存在字符串對象中。反之也可以從一個經(jīng)過ISO-8859-1編碼的字符串對象中取出原始字節(jié)數(shù)據(jù)。例如:
上例的輸出結(jié)果為:
原始字節(jié)流:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD
轉(zhuǎn)換字節(jié)流:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD
這種通過字符串對象保存原始字節(jié)數(shù)據(jù)的方法被很多地方所使用。最常見的就是Java WEB應(yīng)用中Web服務(wù)器對來自于服務(wù)器的表單數(shù)據(jù)的處理,關(guān)于這方面的詳細(xì)說明請參考 如何解決Java WEB應(yīng)用中的亂碼問題。