目录
一、命令行乱码与chcp指令
二、UTF-8与BOM头
三、java编码方式与活动代码页的编码冲突
四、后续补充
参考文献
cmd命令行中有时会发生乱码的情况,这是由于需要显示的字符不在当前代码页的字符集中,在cmd命令行中使用chcp命令可以显示当前代码页。【1】
编号 |
含义 |
---|---|
936 | 简体中文(GBK编码) |
65001 | Unicode(UTF-8) |
437 | 美国英语(DOS默认) |
可以在【3】和【4】上查询UTF-8的相关字符信息
可以使用如下bat脚本验证一个包含UTF-8字符的文件
chcp 65001 %将活动代码页设置为UTF-8,仅一次性有效%
type test.java %显示文件内容,相当于shell命令中的cat%
javac -encoding UTF-8 test.java
java test
echo ã %打印字符%
echo 你好
pause
当文件中包含汉字时,直接编译可能会报错,这时候需要在javac命令中加上encoding选项
javac -encoding UTF-8 test.java
但是此时会报如下错误
这个非法字符其实就是记事本的UTF-8格式中在文件最前面所加的BOM头【6】,而之所以会报错则是因为java内部的UTF-8编码机制中不会对BOM头进行识别,长期以来一直有人建议java改进这一问题,但是Oracle在官网上解释并声明了java现在及以后都不会添加识别BOM头的功能,所有的应用必须自己完成BOM头的识别【7】。一种解决方案是将文本文件再另外保存为Unicode格式并且在编译时也使用Unicode选项,但是这么做依然无法解决部分UTF-8特有文字无法显示的问题,另外一种解决方案就是更换文本文件,使用一种对于格式支持更加强大的软件,比如说notepad++【8】,它支持用户选择是否在UTF-8格式中保留BOM头,只要将文件使用notepad++转换为不包含BOM头的格式,再使用javac -encoding UTF-8编译就可以了,notepad++可以在其官网下载【5】。
在使用notepad++去除BOM头并且修改活动代码页为65001的情况下,一部分java输出依然无法正确显示。
如下test.java程序
public class test{
public static void main(String args[]){
System.out.println("你好");
System.out.println("ã");
}
}
【情形一】执行如下命令(采用默认的活动代码页936,javac编译采用默认编码方式)
type test.java
javac test.java
java test
echo 你好
echo ã
输出情况为:type打印的test.java中的特殊字符乱码,能够编译但是输出乱码,echo命令不能正确打印特殊字符
【情形二】执行如下命令(设置活动代码页65001,javac 采用默认编码方式)
chcp 65001
type test.java
javac test.java
java test
echo 你好
echo ã
输出情况为:全部正确输出
【情形三】执行如下命令(采用默认代码页936,javac采用UTF-8编码选项)
type test.java
javac -encoding UTF-8 test.java
java test
echo 你好
echo ã
输出情况为:type打印的test.java中的特殊字符乱码,能够编译,输出的字符中,汉字正确显示,ã字符乱码,echo命令不能正确打印特殊字符
【情形四】执行如下命令(设置活动代码页65001,javac采用UTF-8编码选项)
chcp 65001
type test.java
javac -encoding UTF-8 test.java
java test
echo 你好
echo ã
输出情况为:type正确打印,能够编译但是无法正确输出,echo正确打印
汇总四种情形如下:
活动代码页 | -encoding 选项 | type结果 | echo结果 | javac编译 | java输出 |
---|---|---|---|---|---|
936 | / | × | × | √ | × |
936 | UTF-8 | × | × | √ | ×(汉字√) |
65001 | / | √ | √ | √ | √ |
65001 | UTF-8 | √ | √ | √ | × |
通过如下语句可以查看java默认的编码方式【9】:
System.out.println(System.getProperty("sun.jnu.encoding"));
我显示的默认编码方式为GBK,所以对于上述表格现象的解释为:type和echo是否能够正确输出取决于当前活动代码页的字符集是否包含需要打印的字符,因此当设置为65001也就是UTF-8时,type和echo都能够正确显示结果;比较奇怪的是,在活动代码页设置为65001时只有采用默认编码方式也就是GBK编码时java才能正确输出,而如果设置encoding选项为UTF-8则反而不能正确输出了。
今天又出现了一个新情况,那就是如果使用java文件新建窗口可视化输出的时候,如果设置不设置encoding选项,则窗口中的输出字符乱码但是命令行输出正确,如果设置encoding选项,则窗口中的输出正确但是命令行无法正确输出,目前没有搞清楚原因,推测可能是由于新建窗口中的默认编码方式和普通字符串的默认编码方式又有所不同。
如果要修改JDK默认编码方式的话,可以参考官网文档【10】,或是这篇文档【11】
另外还有一些字符串编码变换的操作【12】,涉及到Charset集【13】:
String str = new String(strs.getBytes(), "utf-8");
不过我在进行如上操作时报了UnsupportedEncodingException错误【14】,还没有彻底搞清楚原因【15】。
另有关BOM头操作的补充材料见【16】。
【1】Win10 修改cmd命令行窗口UTF-8编码
https://blog.csdn.net/tfs411082561/article/details/78416569
【2】代码页
https://baike.baidu.com/item/%E4%BB%A3%E7%A0%81%E9%A1%B5/11025504?fr=aladdin#6_1
【3】http://www.mytju.com/classcode/tools/encode_utf8.asp
【4】https://www.utf8-chartable.de/
【5】https://notepad-plus-plus.org/
【6】常见编码和编码头BOM
【7】JDK-4508058 : UTF-8 encoding does not recognize initial BOM
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4508058
【8】How to compile a java source file which is encoded as “UTF-8”?
【9】java查询当前操作系统的默认编码方式
https://blog.csdn.net/liangwenmail/article/details/78604731
【10】https://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#tooloptions
【11】将JDK默认编码设置为UTF-8
【12】System.out.println() 输出中文乱码
https://blog.csdn.net/pkuyjxu/article/details/7493801
【13】https://docs.oracle.com/javase/8/docs/api/index.html
【14】https://docs.oracle.com/javase/8/docs/api/java/io/UnsupportedEncodingException.html
【15】Java8 FileSystems.getDefault throw UnsupportedCharsetException
【16】How to use UTF-8, UTF-8 with BOM marker, XML and Java iostreams together
http://akini.mbnet.fi/java/java_utf8_xml/