java编码详解

时间:2022-12-02 20:42:02

举个例子

我们在开发过程中,特别是多种编码格式并存的情况下,很容易遇到乱码问题。 假如有一个GBK编码java文件,然后再使用-Dfile.encoding=GBK参数,写入的文件中哪些是乱码呢。那如果使用UFT-8编码的java文件呢。

public class Main {
static String content = "中文"; public static void main(String[] args) throws IOException {
OutputStreamWriter gbkWriter =new OutputStreamWriter(new FileOutputStream("GBK-FILE"),"GBK"); // 情况1、2、5的结果肯定是一致的
gbkWriter.write(content+"\n"); //(1)
gbkWriter.write(new String(content.getBytes("GBK"),"GBK")+"\n"); // (2)
gbkWriter.write(new String(content.getBytes("GBK"), "UTF-8")+"\n"); // (3)
gbkWriter.write(new String(content.getBytes("UTF-8"),"GBK")+"\n"); // (4)
gbkWriter.write(new String(content.getBytes("UTF-8"), "UTF-8")+"\n"); // (5) gbkWriter.flush();
gbkWriter.close(); }
}

  

java编译到输出

其实用一张图就可以清晰的概括出从java文件编译到输出的过程 java编码详解 主要有3个地方的编码转换:

编译过程

上图①所示的位置,其实就是

javac -encoding xxx

的时候控制,如果你没有显示的指定编码,那么会根据当前操作系统的默认编码格式进行编译,一般windows是gbk,linux是UTF-8。如果这里编码指定错了,那么你的代码很有可能出现中文乱码问题,注意是很有可能,而不是绝对。原因后面会说到。编译出来的class文件统一都是UTF-8格式

运行加载

当class文件加载的jvm的时候,也会进行字符串编码转换,和前面一样,会使用操作系统默认的编码格式,这里的编码不是指class文件的编码,而是指java文件的编码格式,类似于指定java文件是什么编码格式编译为class文件的。就是jvm参数:

java -Dfile.encoding=xxx

在java运行过程中,字符串在内存中则是使用Unicode(UTF-16)进行存储的。而UTF-8转换为UTF-16是很简单的过程。当我们标用String.getBytes()时候,则是把内存中的unicode转换对应的字节数组。(如果没有指定,则使用操作系统默认的编码格式)。可以看出,把一个字符串从编译到内存,其实是经历的过程为:

编译文件编码->加载jvm编码->unicode

输出

输出的编码则是在代码中指定的。例如: OutputStreamWriter gbkWriter =new OutputStreamWriter(new FileOutputStream("GBK-FILE"),"GBK");

例子解析

如果理解上面的,我们再看看文章一开始的例子。举几个例子做说明,其他的情况也就逐类旁通。

例子1

  • java文件编码:GBK,
  • javac -encoding GBK
  • java -Dfile.encoding=GBK 那么使用GBK编码查看输出文件
  • (1)正常
  • (2)正常
  • (3)乱码
  • (4)乱码
  • (5)正常

情况(1)

情况1是比较好理解的,因为java文件编码、编译、加载都是使用GBK,加载到内存中Unicode肯定也是正常的,那么打印出来也是正常的。

情况(2)和情况(5)

在情况1的前提下(即加载到内存中是正常的),在jvm中使用GBK解码在编码肯定是正常的。

情况(3)和情况(4)

在情况1的前提下,使用不同的解码和编码,肯定是乱码

特殊情况

当我们使用UTF-8的格式打开文件的时候,情况(4)是正常的,其余都是乱码。其实是因为先使用unicode进行转换为UTF-8格式的Byte数组,生成的字符串虽然乱码和写文件的格式都是GBK,相当于原封不动的UTF-8格式的byte数组写到文件中,所以就会出现这个情况

小节

这个例子就是直至加载到内存都是正常的情况下,在jvm内进行编码和解码导致乱码的情况

例子2

  • java文件编码:UTF-8,
  • javac -encoding GBK
  • java -Dfile.encoding=UTF-8

那么使用GBK编码查看输出文件

  • (1)乱码
  • (2)乱码
  • (3)正常
  • (4)乱码
  • (5)乱码

情况(1)

情况1是乱码,说明字符串加载到内存中就已经是乱码了。因为UFT-8格式使用GBK进行编码,在生成class文件就已经是乱码了。

情况(3)

情况3为什么又是正常的呢,其实这是误打误撞类型。个人理解的造成这个情况的原因有:

  1. java文件的编码正好和jvm加载文件编码格式是一样的
  2. javac过程,相当于一个UTF-8->GBK格式转换,而content.getBytes("GBK"), "UTF-8")又相当于GBK->UTF-8的转换,两次转换正好相互抵消。

使用UTF-8编码查看输出文件

  • (1)正常
  • (2)正常
  • (3)乱码
  • (4)乱码
  • (5)正常

为什么是使用UTF-8打开情况1不是乱码了呢,其实和上面误打误撞,只不过之前发生在内存中的GBK->UTF-8换为我们在打开文件的时候进行的编码转换,情况1、2、5结果肯定一致的

小结

和例子1不一样,例子2是在生成class文件就已经是乱码的情况。这就是前面所说的,生成class乱码,但输出不一定是乱码。

总结

一句话:一定要保证java文件、编译、加载class文件、输出的编码格式一致。如果出现了乱码,可以从这几个阶段进行排查。