問題來源
最近公司引入容器技術(shù)众弓,按照計(jì)劃將應(yīng)用切換至容器平臺(tái)查近,應(yīng)用切換驗(yàn)證過程中發(fā)現(xiàn)一個(gè)奇怪的問題。原來可以正常解析的XML配置文件,切換后出現(xiàn)了中文亂碼問題钩骇,如果是純英文的則可以正常解析,包含中文的要么解析報(bào)錯(cuò),要么解析出來的中文內(nèi)容亂碼倘屹。
因?yàn)閼?yīng)用層面未做任何調(diào)整银亲,所以問題定位還是相對(duì)容易,直接對(duì)比應(yīng)用的啟動(dòng)參數(shù)就發(fā)現(xiàn)了問題纽匙。原來應(yīng)用部署的參數(shù)未指定-Dfile.encoding
,而切換容器后統(tǒng)一增加了啟動(dòng)參數(shù)-Dfile.encoding=UTF-8
务蝠。而應(yīng)用中XML配置文件使用的是GBK編碼,所以導(dǎo)致了亂碼烛缔,將啟動(dòng)參數(shù)調(diào)整為-Dfile.encoding=GBK
馏段,XML配置文件解析恢復(fù)正常。
雖然問題解決了践瓷,但是任然有三個(gè)困惑點(diǎn)沒有解決:
- 未指定
file.encoding
的情況下院喜,默認(rèn)編碼是由什么決定的? - 指定
file.encoding
的話晕翠,會(huì)產(chǎn)生什么影響喷舀? - 經(jīng)常與
file.encoding
一起出現(xiàn)的sun.jnu.encoding
參數(shù)又是什么?兩者有什么關(guān)系淋肾?
于是決定探索一下-Dfile.encoding
元咙。
啟動(dòng)參數(shù)-Dfile.encoding
是什么?
file.encoding 直譯:文件編碼巫员。
查找 java 源碼,只有四個(gè)類調(diào)用了 file.encoding 這個(gè)屬性甲棍。
- 在
java.nio.Charset.defaultCharset()
/**
* Returns the default charset of this Java virtual machine.
*
* <p> The default charset is determined during virtual-machine startup and
* typically depends upon the locale and charset of the underlying
* operating system.
*
* @return A charset object for the default charset
*
* @since 1.5
*/
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;
}
從注釋中可以看到简识,默認(rèn)字符集是在 java 虛擬機(jī)啟動(dòng)時(shí)決定的,依賴于 java 虛擬機(jī)所在的操作系統(tǒng)的區(qū)域以及字符集感猛。
從代碼中可以看出七扰,默認(rèn)字符集就是從 file.encoding 這個(gè)屬性中獲取的。
此處的默認(rèn)字符集會(huì)影響字符串陪白、文件字符流讀寫等的默認(rèn)編碼颈走。
-
URLEncoder.encode(String)
Web環(huán)境中最常遇到的編碼使用。 -
com.sun.org.apache.xml.internal.serializer.Encoding.getMimeEncodings(String)
影響對(duì)無編碼設(shè)置的xml文件的讀取 咱士。 -
javax.print.DocFlavor
影響打印的編碼立由。
從以上信息可以分析到,file.encoding
會(huì)影響無指定編碼的字符串序厉、讀寫文件锐膜、URL編碼、打印等內(nèi)容弛房。
分析file.encoding
對(duì)字符輸入流的影響
無編碼設(shè)置的字符輸入流方法:java.io.InputStreamReader.InputStreamReader(InputStream in)
的源碼如下:
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
// The default encoding should always be available
throw new Error(e);
}
}
接著看StreamDecoder.forInputStreamReader
的源碼:
public static StreamDecoder forInputStreamReader(InputStream var0, Object var1, String var2) throws UnsupportedEncodingException {
String var3 = var2;
if (var2 == null) {
var3 = Charset.defaultCharset().name();
}
try {
if (Charset.isSupported(var3)) {
return new StreamDecoder(var0, var1, Charset.forName(var3));
}
} catch (IllegalCharsetNameException var5) {
}
throw new UnsupportedEncodingException(var3);
}
到這里就發(fā)現(xiàn)道盏,如果沒有設(shè)置編碼參數(shù),即上面源碼中的if (var2 == null)
,則又回到了開始說的:Charset.defaultCharset()
荷逞,獲取到的默認(rèn)編碼也就是file.encoding
指定的編碼媒咳。
那么問題來了,如果啟動(dòng)參數(shù)中沒有指定file.encoding
的值种远,那jvm啟動(dòng)的時(shí)候file.encoding
指定的默認(rèn)值是什么呢涩澡?
分析file.encoding
參數(shù)默認(rèn)值
說明: 由于很多場(chǎng)景
file.encoding
和sun.jnu.encoding
總是被一起提及,所以下面一起分析這兩個(gè)參數(shù)院促。以下測(cè)試中筏养,操作系統(tǒng)編碼:GBK
,java類文件編碼:UTF-8
常拓。
先看一下未指定啟動(dòng)參數(shù)值的情況下輸出系統(tǒng)參數(shù)file.encoding
和sun.jnu.encoding
的值渐溶。代碼如下:
public class FileEncodeTest {
public static void main(String[] args) {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
}
}
執(zhí)行結(jié)果:
$ javac FileEncodeTest.java
$ java FileEncodeTest
file.encoding : GBK
sun.jnu.encoding : GBK
確認(rèn)一下操作系統(tǒng)當(dāng)前的編碼:
$ env | grep LANG=
LANG=zh_CN.GBK
從結(jié)果來看,file.encoding
和sun.jnu.encoding
的值與操作系統(tǒng)的編碼值一致弄抬。但是并不能說明file.encoding
和sun.jnu.encoding
的默認(rèn)值值由操作系統(tǒng)的編碼決定茎辐。
需要進(jìn)一步驗(yàn)證,將操作系統(tǒng)默認(rèn)編碼調(diào)整為UTF-8:
$ export LANG=zh_CN.UTF-8
$ env|grep LANG=
LANG=zh_CN.UTF-8
重新運(yùn)行得出測(cè)試結(jié)果:
$ java FileEncodeTest
file.encoding : UTF-8
sun.jnu.encoding : UTF-8
調(diào)整操作系統(tǒng)編碼為UTF-8后掂恕,file.encoding
和sun.jnu.encoding
的值也變?yōu)閁TF-8拖陆。
到這里可以得出結(jié)論,file.encoding
和sun.jnu.encoding
的默認(rèn)值由操作系統(tǒng)的當(dāng)前編碼決定懊亡。
分析file.encoding
對(duì)讀寫文件內(nèi)容的影響
通過不設(shè)置編碼格式的FileReader
讀取一個(gè)UTF-8編碼的文件FileEncodeTest副本.java
依啰,打印文件名和文件內(nèi)容〉暝妫【操作系統(tǒng)編碼為:GBK】
import java.io.*;
public class FileEncodeTest {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
// sun.jnu.encoding不會(huì)影響文件名的讀取
// java -Dfile.encoding=utf-8 -Dsun.jnu.encoding=GBK FileEncodeTest 正常讀取文件
// java -Dfile.encoding=utf-8 -Dsun.jnu.encoding=UTF-8 FileEncodeTest 正常讀取文件
File file = new File("D:\\FileEncode\\UTF8\\FileEncodeTest副本.java");
System.out.println(file.getName());
// java -Dfile.encoding=utf-8 -Dsun.jnu.encoding=UTF-8 FileEncodeTest 正常創(chuàng)建文件
// java -Dfile.encoding=utf-8 -Dsun.jnu.encoding=GBK FileEncodeTest 正常創(chuàng)建文件
// java -Dfile.encoding=utf-8 -Dsun.jnu.encoding=ISO-8859-1 FileEncodeTest 正常創(chuàng)建文件
File file01 = new File("E:\\xstl\\中文01.txt");
file01.createNewFile();
// file.encoding會(huì)影響文件內(nèi)容的讀取
FileReader fileReader = new FileReader( "D:\\FileEncode\\UTF8\\FileEncodeTest副本.java");
System.out.println("FileReader Encode : " + fileReader.getEncoding());
BufferedReader br = new BufferedReader(fileReader);
String line;
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}
在不添加file.encoding
啟動(dòng)參數(shù)的情況下速警,文件名正常,文件內(nèi)容亂碼鸯两。
$ javac -encoding utf-8 FileEncodeTest.java
$ java FileEncodeTest
file.encoding : GBK
sun.jnu.encoding : GBK
FileEncodeTest副本.java
FileReader Encode : GBK
public class FileEncodeTest鍓湰 {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
System.out.println("FileEncodeTest鍓湰 ");
}
}
調(diào)整運(yùn)行時(shí)參數(shù)闷旧,增加-Dfile.encoding=UTF-8
后執(zhí)行,不再亂碼钧唐。
$ java -Dfile.encoding=UTF-8 FileEncodeTest
file.encoding : UTF-8
sun.jnu.encoding : GBK
FileEncodeTest副本.java
FileReader Encode : UTF8
public class FileEncodeTest副本 {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
System.out.println("FileEncodeTest副本 ");
}
}
根據(jù)上面的驗(yàn)證忙灼,可以得出結(jié)論,'file.encoding'參數(shù)設(shè)置的編碼會(huì)影響讀取文件的內(nèi)容钝侠,'sun.jnu.encoding'參數(shù)設(shè)置不會(huì)影響讀取文件的文件名该园。
那是否有可能在讀取文件內(nèi)容之前先設(shè)置一下'file.encoding'的值,然后再讀取文件內(nèi)容帅韧,就可以了呢爬范?
JVM啟動(dòng)后再System.setProperty("file.encoding")是否有效果?
稍微調(diào)整一下代碼弱匪,在讀取文件內(nèi)容之前青瀑,先將'file.encoding'的值設(shè)為UTF-8
璧亮,設(shè)置系統(tǒng)屬性值的代碼:System.setProperty("file.encoding", "UTF-8")
。
import java.io.*;
public class FileEncodeTest {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
System.setProperty("file.encoding", "UTF-8");
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
FileReader fileReader = new FileReader( "D:\\FileEncode\\UTF8\\FileEncodeTest副本.java");
System.out.println("FileReader Encode : " + fileReader.getEncoding());
BufferedReader br = new BufferedReader(fileReader);
String line;
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}
根據(jù)輸出結(jié)果可以看出斥难,雖然系統(tǒng)值改變了枝嘶,System.getProperty("file.encoding")
的值變?yōu)榱?code>UTF-8,但是并沒有改變默認(rèn)字符集的值哑诊,FileReader
的編碼依然是GBK
群扶。
$ java FileEncodeTest
file.encoding : GBK
sun.jnu.encoding : GBK
file.encoding : UTF-8
FileEncodeTest副本.java
FileReader Encode :GBK
public class FileEncodeTest鍓湰 {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
System.out.println("FileEncodeTest鍓湰 ");
}
}
因此可以得出結(jié)論,JVM啟動(dòng)后設(shè)置系統(tǒng)配置值System.setProperty("file.encoding", "UTF-8")
不會(huì)影響到默認(rèn)字符集的編碼镀裤。如果需要指定讀取文件內(nèi)容的編碼竞阐,需要通過字符流的構(gòu)造器InputStreamReader(InputStream in, Charset cs)
設(shè)置。
對(duì)類編譯暑劝、加載的影響(內(nèi)容和文件名)
既然file.encoding
的值會(huì)影響文件內(nèi)容讀取的編碼骆莹,而類加載的過程也需要讀取class文件的內(nèi)容,那file.encoding
是否會(huì)影響類加載過程呢担猛?我們先試一下幕垦。下面是測(cè)試代碼【FileEncodeTest.java文件是UTF-8編碼】:
public class FileEncodeTest {
public static void main(String[] args) {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
System.out.println("中文");
}
}
不帶-encoding utf-8
,編譯執(zhí)行傅联,運(yùn)行結(jié)果:
$ javac FileEncodeTest.java
$ java FileEncodeTest
file.encoding : GBK
sun.jnu.encoding : GBK
涓枃
從結(jié)果來看先改,java類文件是UTF-8
編碼,file.encoding
是GBK
蒸走,從而導(dǎo)致了亂碼仇奶,似乎印證了file.encoding
會(huì)影響class文件的加載。
然而事實(shí)并非如此比驻,即使加上參數(shù)'-Dfile.encoding=utf-8'该溯,執(zhí)行結(jié)果依然會(huì)亂碼。
$ java -Dfile.encoding=utf-8 Test02
file.encoding : utf-8
sun.jnu.encoding : GBK
涓枃
細(xì)心的讀者可能會(huì)注意到嫁艇,前面編譯代碼的時(shí)候都增加了參數(shù)-encoding utf-8
,事實(shí)上此處會(huì)亂碼并不是加載的時(shí)候引起的弦撩,而是編譯時(shí)引起的步咪。
調(diào)整編譯參數(shù),增加-encoding utf-8
益楼,重新測(cè)試猾漫。
$ javac -encoding utf-8 FileEncodeTest.java
$ java FileEncodeTest
file.encoding : GBK
sun.jnu.encoding : GBK
中文
編譯恢復(fù)正常。
在類編譯過程中需要指定編譯代碼的編碼感凤,也就是java類文件的編碼悯周。編譯后形成的class文件被統(tǒng)一編碼為UNICODE格式,類加載過程中自然也是使用UNICODE編碼陪竿,file.encoding
影響的是未指定字符編碼時(shí)的默認(rèn)字符集禽翼。
接下來進(jìn)一步驗(yàn)證,先調(diào)整測(cè)試代碼:
public class FileEncodeTest {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
String test = "中文";
System.out.println(new String(test.getBytes(), "UTF-8"));
}
}
運(yùn)行結(jié)果:
$ javac -encoding utf-8 FileEncodeTest.java
$ java FileEncodeTest
file.encoding : GBK
sun.jnu.encoding : GBK
????
因?yàn)槟J(rèn)的字符編碼集是GBK
,new String(test.getBytes(), "UTF-8")
這段代碼闰挡,實(shí)際上是new String(test.getBytes("GBK"), "UTF-8")
锐墙。
調(diào)整執(zhí)行參數(shù),增加-Dfile.encoding=utf-8
长酗,重新運(yùn)行溪北,中文正常輸出:
$ javac -encoding utf-8 FileEncodeTest.java
$ java -Dfile.encoding=utf-8 FileEncodeTest
file.encoding : utf-8
sun.jnu.encoding : GBK
中文
或者將new String(test.getBytes(), "UTF-8")
,調(diào)整為new String(test.getBytes(), "GBK")
夺脾,亂碼問題也可以解決之拨,其實(shí)好的實(shí)踐應(yīng)該是:new String(test.getBytes("UTF-8"), "UTF-8")
。
以上可以得出結(jié)論咧叭,編譯期間的字符編碼由javac -encoding utf-8
決定蚀乔,運(yùn)行期間的默認(rèn)字符編碼由file.encoding
決定,而class文件和JVM的字符編碼統(tǒng)一使用UNICODE
編碼佳簸。
那說半天乙墙,sun.jnu.encoding
一點(diǎn)存在感都沒有,那sun.jnu.encoding
究竟起什么作用呢生均?
中文類名听想?
研究到這里,file.encoding
參數(shù)的作用已經(jīng)比較清楚了马胧,那sun.jnu.encoding
又有什么作用呢汉买?我們先試著運(yùn)行如下測(cè)試代碼:
public class FileEncodeTest副本 {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
}
}
運(yùn)行結(jié)果,一切正常:
$ javac -encoding utf-8 FileEncodeTest副本.java
$ java FileEncodeTest副本
file.encoding : GBK
sun.jnu.encoding : GBK
調(diào)整一下運(yùn)行參數(shù)佩脊,增加'-Dsun.jnu.encoding=utf-8'蛙粘,提示“錯(cuò)誤: 找不到或無法加載主類 FileEncodeTest????”
。
$ java -Dsun.jnu.encoding=utf-8 FileEncodeTest副本
錯(cuò)誤: 找不到或無法加載主類 FileEncodeTest????
這是因?yàn)闇y(cè)試場(chǎng)景的操作系統(tǒng)編碼是GBK
威彰,當(dāng)sun.jnu.encoding
未配置使用和操作系統(tǒng)一致編碼(GBK
)出牧,編碼統(tǒng)一不會(huì)引起亂碼。而手動(dòng)設(shè)置sun.jnu.encoding
為utf-8
編碼時(shí)歇盼,與操作系統(tǒng)的GBK
編碼不一致舔痕,因而無法加載指定的類。
這說明-Dsun.jnu.encoding
的編碼會(huì)影響類加載時(shí)定位中文類豹缀。
另外伯复,我們來看一下下面這個(gè)測(cè)試代碼:
public class FileEncodeTest {
public static void main(String[] args) throws Exception {
System.out.println("file.encoding : "+System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding : "+System.getProperty("sun.jnu.encoding"));
System.out.println("args0 : " + args[0]);
System.out.println(System.getProperties().getProperty("test"));
}
}
運(yùn)行結(jié)果如下:
$ javac -encoding utf-8 FileEncodeTest.java
$ java -Dsun.jnu.encoding=GBK -Dtest=中文 FileEncodeTest 中文
file.encoding : GBK
sun.jnu.encoding : GBK
args0 : 中文
中文
重新調(diào)整運(yùn)行參數(shù),將sun.jnu.encoding
的值從GBK
改為UTF-8
邢笙,再執(zhí)行結(jié)果如下:
$ javac -encoding utf-8 FileEncodeTest.java
$ java -Dsun.jnu.encoding=UTF-8 -Dtest=中文 FileEncodeTest 中文
file.encoding : GBK
sun.jnu.encoding : UTF-8
args0 : ????
中文
從上面的測(cè)試結(jié)果可以看出啸如,'-Dsun.jnu.encoding' 除了影響讀取類名,還會(huì)影響傳入?yún)?shù)的編碼氮惯。
總結(jié)
-
file.encoding
不主動(dòng)配置的情況下叮雳,默認(rèn)是操作系統(tǒng)的編碼想暗; -
file.encoding
在JVM啟動(dòng)后再修改其值,只會(huì)修改配置項(xiàng)值债鸡,不會(huì)改變默認(rèn)字符集編碼江滨; - 運(yùn)行時(shí)配置
file.encoding
,影響java默認(rèn)字符集編碼:
- Charset.defaultCharset() Java環(huán)境中非常關(guān)鍵的編碼設(shè)置
- URLEncoder.encode(String) Web環(huán)境中最常遇到的編碼使用
- com.sun.org.apache.xml.internal.serializer.Encoding 影響對(duì)無編碼設(shè)置的xml文件的讀取
- javax.print.DocFlavor 影響打印的編碼
-
sun.jnu.encoding
影響類加載時(shí)類名的編碼
文件操作涉及到字節(jié)操作和字符操作厌均,在字符操作的時(shí)候應(yīng)該明確指定操作的編碼唬滑,而不是依賴默認(rèn)配置,從而避免很多的不確定性棺弊,降低外部依賴(耦合)晶密。
注意:Eclipse或IDEA在編譯或運(yùn)行時(shí),會(huì)默認(rèn)增加編譯模她、運(yùn)行時(shí)參數(shù)稻艰,會(huì)影響代碼效果,建議在命令行驗(yàn)證如上測(cè)試代碼侈净。