java encoding

这篇记录了一些关于java encoding相关的测试,方便以后查询。

起因是今天在调试自动化测试用例的时候,有同学log message是中文,然后output里面是乱码,问我咋整。说实话之前log输出中文是乱码也知道,但是没注意过。本着负责负责负责的态度,针对这个问题研究了下,也蛮有意思的。

问题描述

  • 环境:git bash中,用mvn的surefire-plugin运行测试用例
  • 有乱码的地方(都是在git bash中)
    • 类编译的过程
    • log4j的输出的中文

分析

结合已知的,可能的影响因素有:

  • windows环境,默认编码GBK
  • javac的-Dfile.encoding(对应的是maven-compile-plugin的source.encoding)
  • log4j的appender的编码设置

这个output的过程其实是:java文件(utf-8)==》指定file.coding编译==》class文件==》log4j输出内容==》git bash展示

第一次测试

脑袋没多想就是编码在某个地方不统一了呗,开搞

首先是源码编译

项目中maven的源码编译是通过maven-compile-plugin的的encoding来配置的,如下:

<plugin>  
    <groupId>org.apache.maven.plugins</groupId>  
    <artifactId>maven-compiler-plugin</artifactId>  
    <version>2.0.2</version>  
    <configuration>  
        <encoding>utf-8</encoding>  
    </configuration>  
</plugin>

尝试改了一把,发现直接导致编译不过去了.原因是源文件都是UTF-8记录的,故乱码应该不是这里的锅.

继续测试log4j编码

修改log4j编码为utf-8

log4j.appender.stdout.Encoding=utf-8

发现诶确实生效了。此时:

  • git bash encoding是utf-8 & java source file是utf-8的

窃喜。但是尝试System.out.println(Charset.defaultCharset()),发现结果是GBK.!!!诶这是什么鬼?

搜一把,影响default charset value的是jvm的一个参数file.encoding。尝试通过配置maven-surefire-plugin

1
<argline>-Dfile.encoding=utf-8</argline>

确实修改了Charset.defaultCharset()的输出为utf-8,但是又变成了乱码!!!

第二次分析

有点蒙,主要针对第二种情况不太明白。为啥Charset.defaultCharset()是utf-8之后反而成了乱码了呢?于是搜java encoding相关的内容,发现一个问题:default-character-encoding-for-java-console-output,其中有一句话点醒了我:

Java will encode bytes using the default encoding set during JVM initialization.

其实不明白的地方就是Charset.defaultCharset()影响了什么:原来是encode的过程。

第二次测试

其实log4j和system.out是类似的,为了简单用system.out做了一把测试,具体代码如下:

public class EncodeTest {

    public static void main(String[] args) {
        // 这里是测试中文输出
        System.out.println("李瑞丰liruifeng");
        System.out.println("default charset is :" + Charset.defaultCharset());
        // 下面其实是为了验证code内部修改file.encoding是否生效
        System.setProperty("file.encoding", "GBK");
        System.out.println("system property :" + System.getProperty("file.encoding"));
        System.out.println("default charset is :" + Charset.defaultCharset());
        System.out.println("李瑞丰liruifeng");
    }
}

在git bash(utf-8)下,运行test1:

$javac -encoding UTF-8 test/test/test/ThreadTest.java
$java -Dfile.encoding=utf-8 test.test.test.ThreadTest
李瑞丰liruifeng
default charset is :UTF-8
system property :GBK
default charset is :UTF-8
李瑞丰liruifeng

此时输出正常。修改javac的encoding为GBK(source file是utf-8),进行test2

$ javac -encoding GBK test/test/test/ThreadTest.java
javac: ▒Ҳ▒▒▒▒ļ▒: test\test\test\ThreadTest.java
▒÷▒: javac <options> <source files>
-help ▒▒▒▒▒г▒▒▒▒ܵ▒ѡ▒▒

出现了编译错误,跟预想一致。使用默认jvm file.encoding(GBK),进行test3

$ java test.test.test.ThreadTest
▒▒▒▒▒liruifeng
default charset is :GBK
system property :GBK
default charset is :GBK
▒▒▒▒▒liruifeng

乱码输出,符合预期。

所以可以简单总结如下:

  • file.encoding决定了defaultCharset的value,默认使用系统编码,在windows是GBK
  • system.out的输入编码和输出编码均由defaultChareset的值决定,故产生乱码的可能性只能是compile编码与defaultCharset值不一致
  • log4j比system.out复杂的地方在于,log4j能单独指定输出的编码,并与Charset.defaultCharset()值无关

所以在第一次测试中,导致问题的原因其实是Charset.defaultCharset()=GBK与compile时encoding=utf-8不一致,source file按utf-8编译为class。但是log4j运行的时候,没指定编码的情况下,是按照Charset.defaultCharset()=GBK尝试去读入&输出,所以就是乱码。而指定了以后绕过了Charset.defaultCharset()=GBK,直接按照utf-8进行读取和输出,故输出正常。此时Charset.defaultCharset()的值的来源是:windows==》git bash==》mvn==》jvm file.encoding

一个彩蛋

搜索过程中发现的一个彩蛋:how-to-find-the-default-charset-encoding-in-java

一句话总结就是:

file.encoding在java代码就当他是readonly的就好了,改了是没有什么卵用的,因为是个cache值:)