关于gradle:IDEA-使用-gradle-乱码之谜

36次阅读

共计 4348 个字符,预计需要花费 11 分钟才能阅读完成。

指标

理解乱码的成因
理解乱码的定位形式和解决办法

为什么须要编码呢?

因为字符串是须要编码成字节数组作为载体的来存储和传输.

为什么会乱码?

乱码产生的起因个别是因为编码转换出错. 字符串常见编码有 GBK 和 UTF- 8 等. 如果一个字符串的编码和解码形式不一样, 就会呈现乱码.

例如是通过 UTF- 8 编码的, 但通过 GBK 来解码, 就会变成上面的样子.
字节数组: [-28, -67, -96, -27, -91, -67]
UTF- 8 解码后: 你好
GBK 解码后: 浣犲ソ

如果是通过 GBK 编码, 但通过 UTF- 8 解码, 就会变成上面的样子.
UTF- 8 解码后: ���
GBK 解码后: 你好

下面是常见的乱码, 能够记住乱码表现形式, 如果是相似的乱码, 就能够大略晓得是什么编码问题了.

如何模仿乱码

如果让你写一个 java 程序, 模仿乱码的状况, 你会怎么写?

java 程序模仿乱码

上面这么写会不会有问题, 在编辑器 (例如 IDEA) 外面的控制台看到的是 “ 浣犲ソ ” 吗?

public static void main(String[] args) throws IOException {System.out.println(new String("你好".getBytes("UTF-8"), "GBK"));

}

答案是 不肯定.

程序编解码剖析

你在控制台看到的字符串通过层层转换最终能力出现后果, 上面 5 局部都会对字符串的出现产生影响:

  1. .class 文件的编码
  2. getBytes(“UTF-8”) 进行转码获取对应编码的字节数组
  3. new String(,”GBK”) 进行解码用于显示字符串
  4. System.out.println 编码后转成字节流写入控制台
  5. 控制台读取字节流数据进行编码后出现在控制台下面

    .class 文件的编码

    .class 编码默认是 UTF- 8 的. 但 .java 不肯定. jdk 编译器会把 .java 转成 .class. 意味着.java 的编码和编译器程序的解码必须是统一的, (IDEA 批改编译编码的形式在备注 1)
    否则会呈现上面的状况, 尽管 .java 外面显示的是 “ 你好 ”, 但实际上变量的内容是 “ 浣犲ソ ”
    !

看着是编译器是没问题的, 但实际上运行起来确是乱码, 证实 .class 呈现问题了

class 外面默认是通过 UTF- 8 编码

getBytes(“UTF-8”)

只有当 class 类编码为 UTF- 8 时, 能力拿到正确的字节数组, 否则编码对不上拿到的字节数组就会有问题.

new String(,”GBK”)

  1. 通过 UTF- 8 编码之后, 通过 GBK 解码即可模拟出乱码的状况.

    System.out.println

  2. , 程序会获取到 控制台的输入流, 并往里面写字节流, 这时候又须要再转一次编码.

    控制台

  3. 控制台利用获取到字节流, 而后通过解码展现在控制台利用上.

编解码总结

下面每个中央都有可能会编码产生影响, 在这个程序外面无奈得悉 1,4,5 的编码到底有没有问题, 所以无奈晓得控制台输入的后果是什么. 看似转来转去很简单, 实际上只须要分明 3 点即可.

  1. 字符串会被编码成字节来存储和传输, 字节是没有乱码. 你看到的中文或者乱码都是通过解码得来的(包含你在编辑器看到的中文).
  2. 在字符串编码之后的字节, 要采纳雷同的解码形式才不会乱码.

编码的中央有很多, 例如存储和传输, 例如输入到文件(.class), 输入到控制台, String.getBytes() 等等. 解码的中央则例如编辑器看到的中文, 控制台的中文, new String() 其实都在解码.

  1. 个别会有三个中央会影响中文的失常出现,

    1. 一个是输出, 例如 .class 文件, socket
    2. 一个是解决, 也就是外部的转换, 例如 String.getBytes() 或者 应用 ByteArrayStream 本人转了一下 .
    3. 一个是输入, 也就是后面提到的解码.

IDEA 应用 gradle 时控制台乱码

最近发现一个 IDEA 外面应用 gradle 插件的一个乱码问题. 上面是特定搞进去的异样, 是编译谬误的异样.

查了很多材料, 通过 IDEA64.exe.vmoptions 外面减少 -Dfile.encoding=UTF-8 能够解决问题. 但发现批改编码后控制台的显示也会有变, 所以有没有更好的形式呢, 乱码的起因是什么呢? 我能不能批改 gradle 的编码方式, 和 IDEA 保持一致, 就不须要批改 -Dfile.encoding 了.

上面的分析方法可能会有点笨, 但如果都搞懂了对乱码的起因会有更深的了解.

IDEA 波及到 gradle 的逻辑

组成部分

在 IDEA 外面 gradle 从运行到展现由三局部组成:

  1. gradle
  2. Gradle Plugin (gradle 的 IDEA 插件)
  3. IDEA console (IDEA 的 run 控制台)

    执行逻辑

    他们的执行逻辑如下:

  4. Gradle Plugin 首先会通过 Process 执行 java gradle-launcher.jar 启动 gradle 的 deamon 过程.
  5. gradle 过程会启动一个端口用于执行真正的 gradle 指令和输入指令后果, 因而 Gradle Plugin 会找到 deamon 凋谢的端口进行 connect, 并传入 gradle 指令.
  6. Gradle Deamon 是一个独立的过程, 被启动后会执行 gradle 指令内容, 例如编译, 执行等, 通过 socket 来返回异样信息. socket 的输入流通过转码出现在 IDEA 的 ConsoleView 下面.

通信形式

他们的通信形式如下

乱码剖析

乱码只会在编解码的中央呈现, 因而一开始须要先找到存在编解码的中央, 而后再一一进行剖析.

可能存在编解码的中央

依据下面的流程能够看到, 中文的源头应该是在 gradle deamon, 因为是 gradle deamon 负责执行 gradle 指令的, 咱们能够揣测出可能存在编码和解码的形式有哪些

  1. JDK JavaCompile 编译产生的异样信息
  2. Gradle Deamon 接管异样信息
  3. gradle deamon-> Gradle Plugin
  4. Gradle Plugin -> IDEA console

一一进行编解码剖析

1. 异样源头

咱们先看这个异样信息是哪里来的. 通过对 gradle 的 debug, 发现异常信息是 gradle 间接调用 JavacTaskImpl 触发编译过程, 而后 jdk 通过流的形式把异样输入进去. , jdk 外面的多语言应用的是 native 的编码方式, jdk 外部的逻辑必定是指定了这种解码形式的. 所以异样信息的解码个别不会有问题.

navite 的编码方式, 让 UTF- 8 编码的字节数组转成可视化的 16 进制的字符串, 再对字符串进行编码保留

debug 发现是间接调用 JDK 外面的 JavacTaskImpl 进行编译, 并通过流的形式输入后果

2. 异样输入

JavaCompile 通过流的形式输入, Gradle Deamon 通过流的形式写入.

上面的框是 JavaCompile 输入流 , 下面的框是 Gradle Deamon 输出流

JavaCompile 输入流

JDK 通过字节流的形式返回编译异样信息, 并应用 Charset.defaultCharset() 来作为编码

Gradle Deamon 输出流

Gradle Deamon 通过 buffer 承受字节流, 而后同样通过 Charset.defaultCharset() 来作为解码

写和读都是应用 Charset.defaultCharset() , 所以不会乱码.

2. socket 通信

Gradle Deamon 的写入

Gradle Deamon 通过读出 javaCompile 的输入流拿到异样的信息, 这时候要通过 socket 传给 Gradle Plugin 了. socket 的序列化形式是通过 kryo 来序列化的, 但在序列化的时候默认应用了 UTF-8 的模式进行编码 (writeUtf8), 而非 Charset.defaultCharset() .

Gradle Plugin 的写出

写完就是 Gradle Plugin 来读写入的信息了, 这里是对 Gradle Plugin 进行 debug 的截图. 因为也是默认应用 UTF- 8 来解码, 所以也没有问题.

类名为: com.esotericsoftware.kryo.io.Input

3. 控制台交互

debug 了一下 Gradle Plugin, 在 ConsoleView 这个类中发现了问题. 读还好好的, 怎么在 ConsoleView 就乱码了.

com.intellij.execution.impl.ConsoleViewImpl

顺着调用链找到失常中文和乱码的两头地带, 发现有个 OutputStreamWriter

为什么两头还要再编码解码一次呢?
因为 gradle 是一个脚本, 因而输入输出都是默认应用流的形式. 依照个别的用法, 会通过命令行去触发指令, 再把输入流写入到管制台上的. 但 Gradle Plugin 刚好是通过本人 connect 的形式而非再起一个过程被动触发, 因而输入输出都在同一个过程外面, 但还是要通过流的形式去获取输入.

OutputStreamWriter 的编码方式是上文提到的 Charset.defaultCharset() , 因为笔者用的是 中文 window, 因而默认是 GBK. 编码没问题, 但读出来的时候缺没依据 Charset.defaultCharset() 来进行编码.

上面的 myBuffer 就是用 GBK 进行编码转成字节数组的, 但 Gradle Plugin 读的时候却用了 UTF-8, 用的是 StringBuilder , toString 只反对 Latin1 和 UTF-8 类型的, 不反对 GBK

解决办法

所以 StringBuilder 的 toString 也是个坑, 居然没有依据 Charset.defaultCharset() 来编码. 也能够说是 Gradle Plugin 的坑, 用了不反对 GBK 的 StringBuilder.
所以能改的只能批改 Gradle Plugin 的编码了, 把后面提到的 GBK 改成 UTF-8, 后面提到改 Charset.defaultCharset() 的形式就是 -Dfile.encoding=UTF-8 , 因为 Gradle Plugin 和 IDEA 是同一个过程, 所以须要批改 IDEA 的 -Dfile.encoding=UTF-8 .

总结和播种

  1. 向下面那样粗疏的定位问题会有点小题大做. 在 java 外面 String 默认都是通过 UTF- 8 编译的, 在控制台看到变量是没有乱码的, 证实编码还是失常的. 因而在 debug 的时候通过查看 String 变量的值是最简略的形式.
  2. 零碎的编码大部分是依据 Charset.defaultCharset() (默认依据操作系统, 可应用 -Dfile.encoding 来指定) 进行编解码的, 这样的益处是零碎外部的编码是对立的, 只有大家都依照 Charset.defaultCharset() 来, 那就不会有问题. 所以咱们编码的时候最好不要指定编码方式, 而是通过 Charset.defaultCharset()来指定, 这样乱码的危险会小一些.

正文完
 0