java運(yùn)行時(shí)參數(shù)file.encoding和sun.jnu.encoding詳解

問(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è)屬性。


  1. 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)編碼。

  1. URLEncoder.encode(String) Web環(huán)境中最常遇到的編碼使用。
  2. com.sun.org.apache.xml.internal.serializer.Encoding.getMimeEncodings(String) 影響對(duì)無(wú)編碼設(shè)置的xml文件的讀取 。
  3. 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.encodingsun.jnu.encoding總是被一起提及,所以下面一起分析這兩個(gè)參數(shù)。以下測(cè)試中,操作系統(tǒng)編碼:GBK,java類(lèi)文件編碼:UTF-8 。

先看一下未指定啟動(dòng)參數(shù)值的情況下輸出系統(tǒng)參數(shù)file.encodingsun.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.encodingsun.jnu.encoding的值與操作系統(tǒng)的編碼值一致。但是并不能說(shuō)明file.encodingsun.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.encodingsun.jnu.encoding的值也變?yōu)閁TF-8。
到這里可以得出結(jié)論,file.encodingsun.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.encodingGBK,從而導(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.encodingutf-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)字符集編碼:
  1. Charset.defaultCharset() Java環(huán)境中非常關(guān)鍵的編碼設(shè)置
  2. URLEncoder.encode(String) Web環(huán)境中最常遇到的編碼使用
  3. com.sun.org.apache.xml.internal.serializer.Encoding 影響對(duì)無(wú)編碼設(shè)置的xml文件的讀取
  4. 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è)試代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容