这篇记录了一些关于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的
<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
|
|
确实修改了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值:)