java中文亂碼解決之道(5):java是如何編碼解碼的

在上篇博客中LZ闡述了java各個(gè)渠道轉(zhuǎn)碼的過程,闡述了java在運(yùn)行過程中那些步驟在進(jìn)行轉(zhuǎn)碼垦沉,在這些轉(zhuǎn)碼過程中如果一處出現(xiàn)問題就很有可能會(huì)產(chǎn)生亂碼挺举!下面LZ就講述java在轉(zhuǎn)碼過程中是如何來(lái)進(jìn)行編碼和解碼操作的。
編碼&解碼
在上篇博客中LZ闡述了三個(gè)渠道的編碼轉(zhuǎn)換過程命斧,下面LZ將結(jié)束java在那些場(chǎng)合需要進(jìn)行編碼和解碼操作近忙,并詳序中間的過程竭业,進(jìn)一步掌握java的編碼和解碼過程。在java中主要有四個(gè)場(chǎng)景需要進(jìn)行編碼解碼操作:
1:I/O操作
2:內(nèi)存
3:數(shù)據(jù)庫(kù)
4:javaWeb
下面主要介紹前面兩種場(chǎng)景及舍,數(shù)據(jù)庫(kù)部分只要設(shè)置正確編碼格式就不會(huì)有什么問題未辆,javaWeb場(chǎng)景過多需要了解URL、get锯玛、POST的編碼咐柜,servlet的解碼,所以javaWeb場(chǎng)景下節(jié)LZ介紹攘残。
I/O操作
在前面LZ就提過亂碼問題無(wú)非就是轉(zhuǎn)碼過程中編碼格式的不統(tǒng)一產(chǎn)生的拙友,比如編碼時(shí)采用UTF-8,解碼采用GBK歼郭,但最根本的原因是字符到字節(jié)或者字節(jié)到字符的轉(zhuǎn)換出問題了遗契,而這中情況的轉(zhuǎn)換最主要的場(chǎng)景就是I/O操作的時(shí)候。當(dāng)然I/O操作主要包括網(wǎng)絡(luò)I/O(也就是javaWeb)和磁盤I/O病曾。網(wǎng)絡(luò)I/O下節(jié)介紹牍蜂。
首先我們先看I/O的編碼操作。


InputStream為字節(jié)輸入流的所有類的超類泰涂,Reader為讀取字符流的抽象類鲫竞。java讀取文件的方式分為按字節(jié)流讀取和按字符流讀取,其中InputStream逼蒙、Reader是這兩種讀取方式的超類从绘。
按字節(jié)
我們一般都是使用InputStream.read()方法在數(shù)據(jù)流中讀取字節(jié)(read()每次都只讀取一個(gè)字節(jié),效率非常慢,我們一般都是使用read(byte[]))僵井,然后保存在一個(gè)byte[]數(shù)組中赁还,最后轉(zhuǎn)換為String。在我們讀取文件時(shí)驹沿,讀取字節(jié)的編碼取決于文件所使用的編碼格式,而在轉(zhuǎn)換為String過程中也會(huì)涉及到編碼的問題蹈胡,如果兩者之間的編碼格式不同可能會(huì)出現(xiàn)問題渊季。例如存在一個(gè)問題test.txt編碼格式為UTF-8,那么通過字節(jié)流讀取文件時(shí)所獲得的數(shù)據(jù)流編碼格式就是UTF-8罚渐,而我們?cè)谵D(zhuǎn)化成String過程中如果不指定編碼格式却汉,則默認(rèn)使用系統(tǒng)編碼格式(GBK)來(lái)解碼操作,由于兩者編碼格式不一致荷并,那么在構(gòu)造String過程肯定會(huì)產(chǎn)生亂碼合砂,如下:
1
2
3
4
5
6
7
8

File file =
new
File(
"C:\test.txt"
);

InputStream input =
new
FileInputStream(file);

StringBuffer buffer =
new
StringBuffer();

byte
[] bytes =
new
byte
[
1024
];

for
(
int
n ; (n = input.read(bytes))!=-
1
; ){

buffer.append(
new
String(bytes,
0
,n));

}

System.out.println(buffer);

輸出結(jié)果:锘挎垜鏄?cm
test.txt中的內(nèi)容為:我是 cm。
要想不出現(xiàn)亂碼源织,在構(gòu)造String過程中指定編碼格式翩伪,使得編碼解碼時(shí)兩者編碼格式保持一致即可:
1

buffer.append(
new
String(bytes,
0
,n,
"UTF-8"
));

按字符
其實(shí)字符流可以看做是一種包裝流,它的底層還是采用字節(jié)流來(lái)讀取字節(jié)谈息,然后它使用指定的編碼方式將讀取字節(jié)解碼為字符缘屹。在java中Reader是讀取字符流的超類。所以從底層上來(lái)看按字節(jié)讀取文件和按字符讀取沒什么區(qū)別侠仇。在讀取的時(shí)候字符讀取每次是讀取留個(gè)字節(jié)轻姿,字節(jié)流每次讀取一個(gè)字節(jié)。
字節(jié)&字符轉(zhuǎn)換
字節(jié)轉(zhuǎn)換為字符一定少不了InputStreamReader逻炊。API解釋如下:InputStreamReader 是字節(jié)流通向字符流的橋梁:它使用指定的 charset

讀取字節(jié)并將其解碼為字符互亮。它使用的字符集可以由名稱指定或顯式給定,或者可以接受平臺(tái)默認(rèn)的字符集余素。 每次調(diào)用 InputStreamReader 中的一個(gè) read() 方法都會(huì)導(dǎo)致從底層輸入流讀取一個(gè)或多個(gè)字節(jié)豹休。要啟用從字節(jié)到字符的有效轉(zhuǎn)換,可以提前從底層流讀取更多的字節(jié)溺森,使其超過滿足當(dāng)前讀取操作所需的字節(jié)慕爬。API解釋非常清楚,InputStreamReader在底層讀取文件時(shí)仍然采用字節(jié)讀取屏积,讀取字節(jié)后它需要根據(jù)一個(gè)指定的編碼格式來(lái)解析為字符医窿,如果沒有指定編碼格式則采用系統(tǒng)默認(rèn)編碼格式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

String file =
"C:\test.txt"
;

String charset =
"UTF-8"
;

// 寫字符換轉(zhuǎn)成字節(jié)流

FileOutputStream outputStream =
new
FileOutputStream(file);

OutputStreamWriter writer =
new
OutputStreamWriter(outputStream, charset);

try
{

writer.write(
"我是 cm"
);

}

finally
{

writer.close();

}

// 讀取字節(jié)轉(zhuǎn)換成字符

FileInputStream inputStream =
new
FileInputStream(file);

InputStreamReader reader =
new
InputStreamReader(inputStream, charset);

StringBuffer buffer =
new
StringBuffer();

char
[] buf =
new
char
[
64
];

int
count =
0
;

try
{

while
((count = reader.read(buf)) != -
1
) {

buffer.append(buf,
0
, count);

}

}

finally
{

reader.close();

}

System.out.println(buffer);

內(nèi)存
首先我們看下面這段簡(jiǎn)單的代碼
1
2
3
4

String s =
"我是 cm"
;

byte
[] bytes = s.getBytes();

String s1 =
new
String(bytes,
"GBK"
);

String s2 =
new
String(bytes);

在這段代碼中我們看到了三處編碼轉(zhuǎn)換過程(一次編碼炊林,兩次解碼)姥卢。先看String.getTytes():
1
2
3

public
byte
[] getBytes() {

return
StringCoding.encode(value,
0
, value.length);

}

內(nèi)部調(diào)用StringCoding.encode()方法操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

static
byte
[] encode(
char
[] ca,
int
off,
int
len) {

String csn = Charset.defaultCharset().name();

try
{

// use charset name encode() variant which provides caching.

return
encode(csn, ca, off, len);

}

catch
(UnsupportedEncodingException x) {

warnUnsupportedCharset(csn);

}

try
{

return
encode(
"ISO-8859-1"
, ca, off, len);

}

catch
(UnsupportedEncodingException x) {

// If this code is hit during VM initialization, MessageUtils is

// the only way we will be able to get any kind of error message.

MessageUtils.err(
"ISO-8859-1 charset not available: "

  • x.toString());

// If we can not find ISO-8859-1 (a required encoding) then things

// are seriously wrong with the installation.

System.exit(
1
);

return
null
;

}

}

encode(char[] paramArrayOfChar, int paramInt1, int paramInt2)方法首先調(diào)用系統(tǒng)的默認(rèn)編碼格式,如果沒有指定編碼格式則默認(rèn)使用ISO-8859-1編碼格式進(jìn)行編碼操作,進(jìn)一步深入如下:
1

String csn = (charsetName ==
null
) ?
"ISO-8859-1"
: charsetName;

同樣的方法可以看到new String 的構(gòu)造函數(shù)內(nèi)部是調(diào)用StringCoding.decode()方法:
1
2
3
4
5
6

public
String(
byte
bytes[],
int
offset,
int
length, Charset charset) {

if
(charset ==
null
)

throw
new
NullPointerException(
"charset"
);

checkBounds(bytes, offset, length);

this
.value = StringCoding.decode(charset, bytes, offset, length);

}

decode方法和encode對(duì)編碼格式的處理是一樣的独榴。
對(duì)于以上兩種情況我們只需要設(shè)置統(tǒng)一的編碼格式一般都不會(huì)產(chǎn)生亂碼問題僧叉。
編碼&編碼格式
首先先看看java編碼類圖[1]


首先根據(jù)指定的chart設(shè)置ChartSet類,然后根據(jù)ChartSet創(chuàng)建ChartSetEncoder對(duì)象棺榔,最后再調(diào)用 CharsetEncoder.encode 對(duì)字符串進(jìn)行編碼瓶堕,不同的編碼類型都會(huì)對(duì)應(yīng)到一個(gè)類中,實(shí)際的編碼過程是在這些類中完成的症歇。下面時(shí)序圖展示詳細(xì)的編碼過程:
201412300002

通過這編碼的類圖和時(shí)序圖可以了解編碼的詳細(xì)過程郎笆。下面將通過一段簡(jiǎn)單的代碼對(duì)ISO-8859-1、GBK忘晤、UTF-8編碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

public
class
Test02 {

public
static
void
main(String[] args)
throws
UnsupportedEncodingException {

String string =
"我是 cm"
;

Test02.printChart(string.toCharArray());

Test02.printChart(string.getBytes(
"ISO-8859-1"
));

Test02.printChart(string.getBytes(
"GBK"
));

Test02.printChart(string.getBytes(
"UTF-8"
)); }

/**

  • char轉(zhuǎn)換為16進(jìn)制

*/

public
static
void
printChart(
char
[] chars){

for
(
int
i =
0
; i < chars.length ; i++){

System.out.print(Integer.toHexString(chars[i]) +
" "
);

}

System.out.println(
""
);

}

/**

  • byte轉(zhuǎn)換為16進(jìn)制

*/

public
static
void
printChart(
byte
[] bytes){

for
(
int
i =
0
; i < bytes.length ; i++){

String hex = Integer.toHexString(bytes[i] &
0xFF
);

if
(hex.length() ==
1
) {

hex =
'0'

  • hex;

}

System.out.print(hex.toUpperCase() +
" "
);

}

System.out.println(
""
);

}

}


outPut:
6211
662f
20
63
6d 3F 3F
20
63
6D CE D2 CA C7
20
63
6D E6
88
91
E6
98
AF
20
63
6D

通過程序我們可以看到“我是 cm”的結(jié)果為:
char[]:6211 662f 20 63 6d
ISO-8859-1:3F 3F 20 63 6DGBK:CE D2 CA C7 20 63 6DUTF-8:E6 88 91 E6 98 AF 20 63 6D
圖如下:


更多&參考文獻(xiàn)
對(duì)于這兩種場(chǎng)景我們只需要設(shè)置一致正確的編碼一般都不會(huì)產(chǎn)生亂碼問題宛蚓,通過LZ上面的闡述對(duì)于java編碼解碼的過程應(yīng)該會(huì)有一個(gè)比較清楚的認(rèn)識(shí)。其實(shí)在java中產(chǎn)生亂碼的主要場(chǎng)景是在javaWeb中设塔,所以LZ下篇博文就來(lái)講解javaWeb中的亂碼產(chǎn)生情形凄吏。
1、Java 編程技術(shù)中漢字問題的分析及解決:http://www.ibm.com/developerworks/cn/java/java_chinese/闰蛔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痕钢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子序六,更是在濱河造成了極大的恐慌盖喷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件难咕,死亡現(xiàn)場(chǎng)離奇詭異课梳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)余佃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門暮刃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人爆土,你說我怎么就攤上這事椭懊。” “怎么了步势?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵氧猬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我坏瘩,道長(zhǎng)盅抚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任倔矾,我火速辦了婚禮妄均,結(jié)果婚禮上柱锹,老公的妹妹穿的比我還像新娘。我一直安慰自己丰包,他們只是感情好禁熏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邑彪,像睡著了一般瞧毙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寄症,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天升筏,我揣著相機(jī)與錄音,去河邊找鬼瘸爽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铅忿,可吹牛的內(nèi)容都是我干的剪决。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼檀训,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柑潦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起峻凫,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渗鬼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后荧琼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體譬胎,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年命锄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堰乔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脐恩,死狀恐怖镐侯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驶冒,我是刑警寧澤苟翻,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站骗污,受9級(jí)特大地震影響崇猫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜需忿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一邓尤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦汞扎、人聲如沸季稳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)景鼠。三九已至,卻和暖如春痹扇,著一層夾襖步出監(jiān)牢的瞬間铛漓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工鲫构, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浓恶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓结笨,卻偏偏與公主長(zhǎng)得像包晰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炕吸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容