初窥java乱码问题
故事起源于这周踩的一个小坑,tomcat本地调启动web时报错。错误提示说有个xml文件的编码有问题,我点进去看了看没看出啥异常,开头跟其他xml一样指定了utf-8的格式,除了是小写的,我傻乎乎地去改成了大写。。。然后。。。肯定是没解决啊!!!咳咳,果断请教同事去了,瞅一眼,扔过来一串参数-Dfile.encoding=UTF-8,让我在webserver的VM OPTION里面加上,成了。
恩,又是那个古老的梗,(咦,好了!但是这是为什么呢。。。)虽然从头到尾我都不懂,但是还是筛选掉一些例如-D是什么鬼之类的问题吧。O(∩_∩)O。。。问题的挖掘经历了几个阶段:
1.file.encoding的系统默认值是什么(其实我也不知道有没有系统默认值这么个东西。。总之就是默认值了啊。。。哈哈。。。)
首先自然想知道默认的encoding值是什么了。我新建了一个空的工程,随便写了个类,在main里面打印System.getproperty("file.encoding")了。结果让我莫名其妙,明明就是UTF-8啊。那系统默认的输出编码格式应该就是UTF-8,应该不会出问题啊。默认的encoding为何没有用到?
2.file.encoding在哪些环节会用到
找到一篇很好地文章http://cmsblogs.com/?p=1475。文章很系统地分析了java输出格式的流程。在不同情况下的流程也是不同的。文章举例了三种情况:1)直接输出到console 2)部署到webserver输出到web页面 3)输出到数据库。这篇文章解决了我的第一个问题,也就是为什么输出到web没有用到那个UTF-8。因为System.getproperty()中得到的file.encoding代表的是输出到console时编码格式,自然跟web页面没关系。
此外我还知道了,java虚拟机在处理数据时,数据在内存中存储的格式均为Unicode,包括.class文件以及外部输入的数据。这样我可以理解成,数据在内存中一律是以Unicode格式流转的,需要输出到哪个地方就再获取对应的file.encoding,将Unicode格式的数据再转换成file.encoding的格式输出。仔细一想,其实这种方式也非常符合java的平台无关特性,毕竟有Unicode这种大杀器(能涵盖所有字符的编码格式我也是醉了),那自然要用它来作为统一的数据处理格式,至于那些关乎平台关乎场景的case,再各自分情况处理。
3.所谓的默认file.encoding值是从哪得到的
说实话这是让我非常头疼的问题,因为臣妾实在是对工具神马的一窍不通啊!!鬼知道TMD写在哪个配置文件的哪个角落里啊!!尼玛,我继续搜,不停地搜。好不容易找到了。。由IDE控制的针对console的file.encoding默认设置。我用的是intellij,在$ItellijHOME/Contents/Info.plist,(这个HOME是我瞎BB的,看得懂就好了啊。。),确实找到了这么一串
<key>VMOptions</key>
<string>-Dfile.encoding=UTF-8 -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -Xverify:none -Xbootclasspath/a:../lib/boot.jar</string>
<key>WorkingDirectory</key>
看到那个“-Dfile.encoding=UTF-8”木有!!成就感满满有木有!!!故事告一段落,最开始找到的那个UTF-8来自于IDE的默认设置,并且管的是console,web容器的file.encoding在tomcat运行设置的VM OPTION重新指定为UTF-8,于是输出的web页面格式OK了。
但是!怎么可以就此止步!不是有getProperty嘛,那也有setProperty啊,你不是说输出到哪就获取对应的file.encoding然后转换unicode到file.encoding再输出么,那我先按utf-8输出,再setProperty成别的编码格式,再输出相同内容是不是就乱码了?
事实证明,企图用setproperty来修改runtime的file.encoding根本不起作用,该输出啥还是输出啥。于是我查了一下资料,file.encoding的值在JVM初始化的时候指定的才是有效地,运行时file.encoding压根就是一个只读属性,据说是初始化的时候存了一个cache,以后即使改了也只会读取cache也就是初始化的值,有兴趣的可以去读一下源码,相关的讨论链接在这了http://stackoverflow.com/questions/1749064/how-to-find-the-default-charset-encoding-in-java。自己也照着试了一下,的确如此,我只有修改vm option才会输出乱码。关键的几句代码:
new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding();//查看有效的file.encoding,也就是初始化时候的那个cache值
System.getProperty("file.encoding");//查看file.encoding的真实值,虽然真实,但是无效啊。。
以上。