問題引入
文件中的文本以UTF-8的編碼方式存儲,在Java程序中以GBK的編碼方式從文件中讀入,最后再將讀入的內(nèi)容轉(zhuǎn)換為UTF-8編碼,即UTF-8 --> GBK --> UTF-8
。這種操作方式能正確讀入文件中的內(nèi)容嗎?
背景知識
因為本文主要討論不同的編碼之間的轉(zhuǎn)換問題,所以有必要先介紹一下文中會用到的幾種編碼方式。
編碼和解碼
將某個字符映射成計算機能存儲和處理的二進制數(shù)的過程稱為編碼,比如字符A
的ASCII編碼為b0100 0001
,我們通常用十六進制來表示成0x41
;將某個二進制數(shù)映射成人類可讀的字符的過程稱為解碼,編碼的逆過程就是解碼。
UTF-8
UTF-8編碼兼容ASCII編碼,也就是說任何一個ASCII編碼的字符,也是UTF-8編碼的字符。比如字符A
的ASCII編碼的十六進制表示為0x41
,占1個字節(jié),那么字符A
的UTF-8編碼也是0x41
,占1個字節(jié)。對于一個漢字來說,需要用三個字節(jié)存儲UTF-8編碼,大家可以通過UTF8在線網(wǎng)站獲得任意字符的UTF-8編碼。
GBK
和UTF-8一樣,GBK也是兼容ASCII編碼的。對于一個漢字來說,需要用兩個字節(jié)存儲GBK編碼,大家可以通過GBK在線網(wǎng)站獲得任意漢字的GBK編碼。
Unicode
和UTF-8一樣,Unicode也是兼容ASCII編碼的。對于一個漢字來說,需要用兩個字節(jié)存儲Unicode編碼,大家可以通過Unicode在線網(wǎng)站獲得任意漢字的Unicode編碼。也可以通過GBK2Unicode在線網(wǎng)站獲得某個GBK編碼對應的Unicode編碼。
實驗與分析
為了回答上面的問題,我編寫了兩種不同的文本內(nèi)容,也就是后面的實驗設置1和實驗設置2,以此來看一下對于不同類型的文本內(nèi)容,采用上述方式是否都可以正確的讀取。因為Java中對文本文件的讀取主要有兩種類型:讀入到字符串和讀入到字符數(shù)組,因此我分別實現(xiàn)了這兩種讀取方式,也就是后面的實驗代碼1和實驗代碼2,以此來探究不同的讀取方式對實驗結(jié)果是否有影響。
實驗環(huán)境
- JDK:16
- OS:Monterey 12.2.1
- 系統(tǒng)默認編碼:UTF-8
實驗代碼1
//將文件中的內(nèi)容讀入到字符串
try(BufferedReader br = new BufferedReader(new FileReader(fileName, Charset.forName("GBK")))){
String content = br.readLine();
byte[] bytes = content.getBytes("GBK");
String str = new String(bytes, "UTF-8");
System.out.println(str);
}
實驗代碼2
//將文件中的內(nèi)容讀入到字符數(shù)組
try(Reader reader = new FileReader(fileName, Charset.forName("GBK"))){
char[] content = new char[2];
reader.read(content);
String str = new String(content);
byte[] bytes = str.getBytes("GBK");
str = new String(bytes, "UTF-8");
System.out.println(str);
}
實驗設置1
文件的編碼方式為UTF8,文件的內(nèi)容為:
你
此設置下,文件中只有一個用UTF-8編碼的漢字你
。我們知道,在UTF-8編碼下,每個漢字占3個字節(jié),大家可以自行檢查一下文件的大小是否為3字節(jié),以避免因為其它的字符(如空格等)影響復現(xiàn)實驗的效果。
在第一個份代碼中(將文件內(nèi)容讀入到字符串),通過單步調(diào)試可以看到,content
的內(nèi)容為浣?
,和文件中的內(nèi)容不一樣,而且看起來有點像亂碼。繼續(xù)單步運行,將字符串按照GBK的編碼方式轉(zhuǎn)換為字節(jié)數(shù)組。此處需要指定編碼方式為GBK的原因是,我們從文件中讀取的時候指定了GBK的解碼方式,因此,想要轉(zhuǎn)換成字節(jié)數(shù)組也得按照GBK的方式編碼。完成轉(zhuǎn)換之后,得到的字節(jié)數(shù)組bytes
的大小為3?,F(xiàn)在我們已經(jīng)有了GBK編碼之后的字節(jié)數(shù)組bytes
了,最后將其按照UFT8的方式解碼成字符串,得到??
??礃幼游覀儧]能正確的讀取到文本文件的內(nèi)容,下面分析一下原因。
漢字你
的UTF8編碼可以通過之前提到的在線網(wǎng)站得到,其為e4 bd a0
,因為Java程序采用GBK的解碼方式讀取文件,所以當讀到第一個字節(jié)e4
時,判斷出還需要再讀一個字節(jié)才能構(gòu)成完整的編碼,因此現(xiàn)在讀入的內(nèi)容是e4 bd
,通過在線網(wǎng)站知道它對應于漢字浣
。因為Java程序內(nèi)部采用Unicode編碼存儲字符串,所以需要將浣
轉(zhuǎn)換為Unicode編碼,通過在線網(wǎng)站可知它對應的Unicode編碼為6d 63
。要注意,實際上Java程序并不需要先把GBK碼解碼成字符,然后再根據(jù)字符做Unicode編碼,而是直接由GBK碼映射到表示相同字符的Unicode碼。Java程序繼續(xù)讀入第三個字節(jié)a0
,同時判斷出a0
不是一個合法的GBK編碼,但是此時已經(jīng)讀到文件的末尾了,無法繼續(xù)讀入下一個字節(jié)。對于此種情況,Java會將非法的GBK編碼映射成默認的Unicode碼ff fd
,此編碼對應的字符是?
??梢钥吹?,這種映射具有不可逆性,我們無法從ff fd
映射回非法的GBK碼,也就是說,這種映射會改變我們的原始數(shù)據(jù)。分析到這兒,我們應該知道,字符串變量content
在內(nèi)存中的實際內(nèi)容為6d 63 ff fd
(不去考慮大端小端的問題),共四個字節(jié),表示兩個字符浣
和?
。
接下來將字符串變量content
按照GBK的編碼方式轉(zhuǎn)換成字節(jié)數(shù)組。前面說過,content
的內(nèi)容為浣?
,首先轉(zhuǎn)換第一個字符浣
,其對應的GBK編碼為e4 bd
。接下來轉(zhuǎn)換第二個字符?
,很遺憾的是,此字符并沒有被GBK收錄,也就是說這個字符沒有對應的GBK編碼,此種情況下會將其轉(zhuǎn)換為GBK編碼3f
,它對應的字符是?
。因此,字節(jié)數(shù)組bytes = [e4, bd, 3f]
。
最后一步將字節(jié)數(shù)組按照UTF-8的方式解碼。當遇到第一個字節(jié)e4
時,發(fā)現(xiàn)它不是一個合法的UTF-8編碼,因此繼續(xù)讀入下一個字節(jié),然后發(fā)現(xiàn)e4 bd
也不是一個合法的UTF-8編碼,繼續(xù)讀入,發(fā)現(xiàn)e4 bd 3f
仍然不是一個合法的編碼,但是已沒有更多的字節(jié)可以讀了。不過UTF-8解碼器發(fā)現(xiàn)e4 bd 3f
雖然不是一個合法的編碼,但3f
卻是一個合法的UTF-8編碼,它對應于字符?
。此時解碼器會選擇將3f
解碼為?
,而認為e4 bd
是一個錯誤的編碼,并將其解碼為默認字符?
。因此,最終得到的解碼結(jié)果為??
。
以上是對運行實驗代碼1時的分析,也就是將文本內(nèi)容讀入到字符串中的情況。對于將文本內(nèi)容讀入到字符數(shù)組的情況,分析過程和實驗代碼1差不多,只是在某些地方有細微的差別。下面簡要的分析一下運行實驗代碼2時的情況。
文件中存儲的數(shù)據(jù)仍然是e4 bd a0
,對e4 bd
按照GBK解碼得到浣
,因為Java中的char
類型也是和String
類型一樣,按照Unicode的編碼方式存儲,浣
對應的Unicode碼為6d 63
,所以字符數(shù)組中的第一個字符在內(nèi)存中實際存儲的是6d 63
。接下來對a0
按照GBK解碼,如前所述,a0
不是一個合法的GBK碼,對于此種情況,字符數(shù)組會舍棄非法的編碼,因此字符數(shù)組的第二個字符仍然是默認的00 00
。下一步中的將字符數(shù)組變?yōu)樽址哪康氖欠奖戕D(zhuǎn)換為字節(jié)數(shù)組,經(jīng)過轉(zhuǎn)換之后得到的字節(jié)數(shù)組bytes = [e4, bd, 00, 00]
。最后一步將字節(jié)數(shù)組按照UTF-8的解碼方式轉(zhuǎn)化成字符串,最終得到的解碼結(jié)果為?
。
實驗設置2
文件的編碼方式為UTF8,文件的內(nèi)容為:
你A
首先分析代碼1。文件內(nèi)容你A
的UTF-8編碼為e4 bd a0 41
,按照GBK解碼得到浣燗
,其對應的Unicode碼為6d 63 71 d7
。 因為字符串使用Unicode編碼進行存儲,所以content
在內(nèi)存中存儲的內(nèi)容為6d 63 71 d7
。之后對content
按GBK編碼獲得對應的字節(jié)數(shù)組,這就又回到了e4 bd a0 41
,即bytes = [e4, bd, a0, 41]
。最后對此字節(jié)數(shù)組按UTF-8解碼得到你A
。
對于代碼2的分析,因為e4 bd
和a0 41
都是合法的GBK編碼,所以不存在丟棄數(shù)據(jù)的問題,那么代碼2的分析就和代碼1的分析完全相同。文章來源:http://www.zghlxwxcb.cn/news/detail-469995.html
結(jié)論
文件的編碼方式是什么,在讀取的時候就要指定什么樣的編碼方式。文章來源地址http://www.zghlxwxcb.cn/news/detail-469995.html
到了這里,關于Java讀寫文件時的GBK和UTF8轉(zhuǎn)換問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!