file.encoding详解

为什么每天都有解决不完的问题,一个问题延伸出来十个问题。虽然大都可以解决,但是也只是停留在解决的层面,终究不是学习之道。
技术不停变更,我相信最本质的原理也就那么几个,一定要挖祖坟 似的学习!
一步一步慢慢来吧~


首先来做几个简单的测试:

测试一

javac -encoding : -encoding <编码> ---- 指定源文件使用的字符编码

对于如下代码:

public class Demo {
	public static void main(String[] args) {
	System.out.println(System.getProperty("file.encoding"));
		System.out.println("hello,中国");
	}
}

格式以UTF-8无BOM类型保存
这里写图片描述

我们分别使用cmd(默认GBK编码)和MinTTY(UTF-8编码)来读取该文件

这里写图片描述

-encoding encoding Set the source file encoding name, such as EUC-JP and UTF-8. If -encoding is not specified, the platform default converter is used.

也就是说,如果不指定encoding,则使用平台默认的编码转换器。
在简体中文的Windows上,平台默认编码会是GBK,那么javac就会默认假定输入的Java源码文件是以GBK编码的。
我们先来证明这句话:

javac Demo.java

对应的字节码文件:
这里写图片描述

我们模拟下以上16进制的获得:

String str = new String("中国".getBytes("UTF-8"), "GBK");
System.out.println(str);//涓浗
System.out.println(DatatypeConverter.printHexBinary(str.getBytes("UTF-8")));//E6B693EE859EE6B597

javac -encoding GBK Demo.java

对应的字节码文件:
这里写图片描述

如果实际输入的确实是 GBK 编码(GBK兼容ASCII编码)的文件,那么一切都会正常。
但如果实际输入的是别的编码的文件,例如UTF-8编码的文件,那 javac 读进来的内容就会出问题,就“乱码”了。乱码的原因:使用 GBK 解码 UTF-8 编码的文件。
这里写图片描述


测试二

java -Dfile.encoding 是用来干什么的?

javac -encoding GBK Demo.java

在 windows 平台生成 class 文件,拖到 Ubuntu 操作系统中。
这里写图片描述

Cygwin下的测试结果:
这里写图片描述

cmd下的测试结果:
这里写图片描述


这里写图片描述

针对以上这个图做一些解释:

①、A.java就是一个文本文件(以某种编码格式来存储:UTF-8、GBK、ISO-8859-1等),java编译器要解析这个文本文件并编译生成.class文件。而要想解析它,就必须知道它的编码方式。(javac - encoding charset)

②:以不同编码方式编码的A.java经过Java编译器编译生成了同一个相同的A.class。(字符串以UTF-8格式存储)
字节码解读见:http://blog.csdn.net/x_iya/article/details/77073112

③:java虚拟机以二进制字节流的形式加载A.class,读取该字符串并构建String。

④:输出结果。

可以知道只有①和③才会导致乱码的产生。
④产生的乱码与接收环境有关,故不做讨论。

从上图可以理解不管采用哪种格式编码的源文件(.java),只要正确告诉编译器,编译器就会得到正确的结果(.class)。同时只要告诉JVM正确的输出流需要的编码格式,JVM总会返回正确编码格式的输出流。
那么要想不产生乱码要注意两个环节:

  1. 告诉编译器(javac -encoding)你的源文件编码格式。
  2. 告诉 JVM 字符串编码方式。(java -Dfile.encoding)
    即两者保持一致。

对于以UTF-8编码的源文件A.java,只要:

javac -encoding UTF-8 A.java
java -Dfile.encoding=UTF-8 A

便可以解决乱码问题。
也许你会说命令行下依旧是乱码,那是命令行的问题(chcp 936),调整为chcp 65001, 或者在Cygwin命令行下使用。
另外String.getBytes() 等价于 String.getBytes(Charset.defaultCharset()) ,在实际编程中推荐使用带参数的。Charset.defaultCharset() 在Windows中文操作系统中是GBK。
当然也不是一成不变的,可以使用java -Dfile.encoding 指定。

@Test
    public void testAvailableCharsets() {
        //Java虚拟机编码方式
        System.out.println("defaultCharset:" + Charset.defaultCharset().name());
        //所有Java支持的字符集
        Charset.availableCharsets().forEach((s, charset) -> System.out.println(s + ":" + charset));

    }

模拟下Java读取、编译、运行*.java 的过程:

import javax.xml.bind.DatatypeConverter;
import java.io.UnsupportedEncodingException;

public class Test {
    /**
     * 模拟 javac -encoding
     * 实现 *.java --> *.class
     *
     * @param str      字符串
     * @param charset  实际字符串编码格式
     * @param encoding 输入编码参数
     * @return *.class 字节里的字节序列
     */
    private static byte[] compile(String str, String charset, String encoding) {
        try {
            //读取(对应于图中的①)
            str = new String(str.getBytes(charset), encoding);
            //编码到*.class中(对应于图中的②)
            return str.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    //模拟结果输出
    private static String run(byte[] buf, String charset, String encoding) {
        try {
            //对应于图中的③
            String str = new String(buf, "UTF-8");
            //对应于图中的④
			return new String(str.getBytes(encoding), charset);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        String str = "中国";
        String charset = "UTF-8";
        String encoding = "GBK";


        //等价于 javac -encoding %encoding% *.java(以 charset 格式编码的源文件)
        // compileBytes 数组即为 str 在 *.class 的表示
        byte[] compileBytes = compile(str, charset, encoding);
        System.out.println(DatatypeConverter.printHexBinary(compileBytes));

        System.out.println(run(compileBytes, charset, encoding));
        
    }
}

参考:
https://www.zhihu.com/question/30977092

http://abingsky37.github.io/java_encode.html

http://www.wuxinjian.com/2017/01/08/Java中的字符编码与乱码/

http://blog.csdn.net/u010234516/article/details/52842170

我们遇到的很多核心或者本质问题,也就那一小撮人能够解决。而我们要做的就是要成为那一小撮人。
在此感谢下RednaxelaFX大神。

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

N3verL4nd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值