問(wèn)題來(lái)源
最近公司引入容器技術(shù),按照計(jì)劃將應(yīng)用切換至容器平臺(tái),應(yīng)用切換驗(yàn)證過(guò)程中發(fā)現(xiàn)一個(gè)奇怪的問(wèn)題。原來(lái)可以正常解析的XML配置文件,切換后出現(xiàn)了中文亂碼問(wèn)題,如果是純英文的則可以正常解析,包含中文的要么解析報(bào)錯(cuò),要么解析出來(lái)的中文內(nèi)容亂碼。
因?yàn)閼?yīng)用層面未做任何調(diào)整,所以問(wèn)題定位還是相對(duì)容易,直接對(duì)比應(yīng)用的啟動(dòng)參數(shù)就發(fā)現(xiàn)了問(wèn)題。原來(lái)應(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ù)正常。
雖然問(wèn)題解決了,但是任然有三個(gè)困惑點(diǎn)沒(méi)有解決:
- 未指定
file.encoding的情況下,默認(rèn)編碼是由什么決定的? - 指定
file.encoding的話,會(huì)產(chǎn)生什么影響? - 經(jīng)常與
file.encoding一起出現(xiàn)的sun.jnu.encoding參數(shù)又是什么??jī)烧哂惺裁搓P(guān)系?
于是決定探索一下-Dfile.encoding。
啟動(dòng)參數(shù)-Dfile.encoding是什么?
file.encoding 直譯:文件編碼。
查找 java 源碼,只有四個(gè)類(lèi)調(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í)決定的,依賴(lài)于 java 虛擬機(jī)所在的操作系統(tǒng)的區(qū)域以及字符集。
從代碼中可以看出,默認(rèn)字符集就是從 file.encoding 這個(gè)屬性中獲取的。
此處的默認(rèn)字符集會(huì)影響字符串、文件字符流讀寫(xiě)等的默認(rèn)編碼。
-
URLEncoder.encode(String)Web環(huán)境中最常遇到的編碼使用。 -
com.sun.org.apache.xml.internal.serializer.Encoding.getMimeEncodings(String)影響對(duì)無(wú)編碼設(shè)置的xml文件的讀取 。 -
javax.print.DocFlavor影響打印的編碼。
從以上信息可以分析到,file.encoding 會(huì)影響無(wú)指定編碼的字符串、讀寫(xiě)文件、URL編碼、打印等內(nèi)容。
分析file.encoding 對(duì)字符輸入流的影響
無(wú)編碼設(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),如果沒(méi)有設(shè)置編碼參數(shù),即上面源碼中的if (var2 == null),則又回到了開(kāi)始說(shuō)的:Charset.defaultCharset(),獲取到的默認(rèn)編碼也就是file.encoding 指定的編碼。
那么問(wèn)題來(lái)了,如果啟動(dòng)參數(shù)中沒(méi)有指定file.encoding 的值,那jvm啟動(dòng)的時(shí)候file.encoding 指定的默認(rèn)值是什么呢?
分析file.encoding 參數(shù)默認(rèn)值
說(shuō)明: 由于很多場(chǎng)景
file.encoding和sun.jnu.encoding總是被一起提及,所以下面一起分析這兩個(gè)參數(shù)。以下測(cè)試中,操作系統(tǒng)編碼:GBK,java類(lèi)文件編碼: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é)果來(lái)看,file.encoding和sun.jnu.encoding的值與操作系統(tǒng)的編碼值一致。但是并不能說(shuō)明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ì)讀寫(xiě)文件內(nèi)容的影響
通過(guò)不設(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,但是并沒(méi)有改變默認(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)容的編碼,需要通過(guò)字符流的構(gòu)造器InputStreamReader(InputStream in, Charset cs)設(shè)置。
對(duì)類(lèi)編譯、加載的影響(內(nèi)容和文件名)
既然file.encoding的值會(huì)影響文件內(nèi)容讀取的編碼,而類(lèi)加載的過(guò)程也需要讀取class文件的內(nèi)容,那file.encoding是否會(huì)影響類(lèi)加載過(guò)程呢?我們先試一下。下面是測(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é)果來(lái)看,java類(lèi)文件是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ù)正常。
在類(lèi)編譯過(guò)程中需要指定編譯代碼的編碼,也就是java類(lèi)文件的編碼。編譯后形成的class文件被統(tǒng)一編碼為UNICODE格式,類(lèi)加載過(guò)程中自然也是使用UNICODE編碼,file.encoding影響的是未指定字符編碼時(shí)的默認(rèn)字符集。

接下來(lái)進(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"),亂碼問(wèn)題也可以解決,其實(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編碼。
那說(shuō)半天,sun.jnu.encoding一點(diǎn)存在感都沒(méi)有,那sun.jnu.encoding究竟起什么作用呢?
中文類(lèi)名?
研究到這里,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ò)誤: 找不到或無(wú)法加載主類(lèi) FileEncodeTest????”。
$ java -Dsun.jnu.encoding=utf-8 FileEncodeTest副本
錯(cuò)誤: 找不到或無(wú)法加載主類(lèi) 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編碼不一致,因而無(wú)法加載指定的類(lèi)。
這說(shuō)明-Dsun.jnu.encoding的編碼會(huì)影響類(lèi)加載時(shí)定位中文類(lèi)。
另外,我們來(lái)看一下下面這個(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' 除了影響讀取類(lèi)名,還會(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ì)無(wú)編碼設(shè)置的xml文件的讀取
- javax.print.DocFlavor 影響打印的編碼
-
sun.jnu.encoding影響類(lèi)加載時(shí)類(lèi)名的編碼
文件操作涉及到字節(jié)操作和字符操作,在字符操作的時(shí)候應(yīng)該明確指定操作的編碼,而不是依賴(lài)默認(rèn)配置,從而避免很多的不確定性,降低外部依賴(lài)(耦合)。
注意:Eclipse或IDEA在編譯或運(yùn)行時(shí),會(huì)默認(rèn)增加編譯、運(yùn)行時(shí)參數(shù),會(huì)影響代碼效果,建議在命令行驗(yàn)證如上測(cè)試代碼。