2127 字
约 7 分钟
4
S01-J06 · 编码与字符集排障:一次讲清乱码的四种成因

适用读者:控制台出现"锟斤拷""????"、配置文件中文变方块、日志里中文成乱码的人。
前置:S01-B04(File Encodings 设置)。
实测环境:JDK 21.0.12.1 / Ubuntu 24.04.1(编码属性与 GBK 错配为 2026-11-05 实测);Windows 部分为文档口径。
覆盖旧文:A-1377(IDEA 控制台中文乱码 4 种解决方案,35 点)。
新增扩展:E-S01-021(file.encoding 与 JDK 18 默认 UTF-8)、E-S01-022(Windows 代码页与 locale)。
上一篇:S01-J05 · IDE 性能与大工程:内存、索引、插件成本
下一篇:S01-J07 · 依赖治理。


1. 一句话结论

乱码从来不是"某处设错了",而是"写入用的编码 ≠ 读取用的编码"。 所以排障只有一件事:确定字节是按什么编码写的,再确定是谁按什么编码读的。

四层,各管一段:

① 源文件字节  ──► ② 编译期(javac 读写源文件)──► ③ 运行时(JVM 默认编码)──► ④ 显示端(IDE 控制台 / 终端)
   存成 UTF-8?        -encoding/-Dfile.encoding        file.encoding/stdout.encoding        chcp / locale / 字体

改了某一层还在乱码,说明问题在另一层。 这是旧文"4 种方案挨个试"的根本缺陷——没有定位,只能穷举。


2. 先把"当前是什么编码"查清楚(实测)

$ java -XshowSettings:properties -version 2>&1 | grep -E "file.encoding|native.encoding|sun.jnu.encoding|stdout.encoding"
    file.encoding = UTF-8
    native.encoding = UTF-8
    stdout.encoding = UTF-8
    sun.jnu.encoding = UTF-8
属性 含义 谁在用
file.encoding JVM 默认字符集(读写文件、new String(bytes) 的默认) 你的代码、老依赖
native.encoding 操作系统本地编码 JDK 内部、部分 native 库
stdout.encoding 标准输出的编码 System.out 打日志到控制台
sun.jnu.encoding 文件名/路径编码 读写中文文件名

JDK 18 起 file.encoding 默认就是 UTF-8(JEP 400),所以很多老项目"以前要加 -Dfile.encoding=UTF-8,现在不加也对"。但也因此,在 Windows 上跑老代码时会出现"以前没事、现在乱了"——因为以前默认是 GBK(当前代码页),现在默认 UTF-8。

旧文勘误:A-1377 把"修改 IDEA 配置文件加 -Dfile.encoding=UTF-8"作为一条通用方案。在 JDK 18+ 上这条通常已无必要;真正需要它的场景是"JDK 8/11 + Windows GBK 代码页"。版本不同,答案不同——这条差异旧文没有标注。


3. 实验:同一份字节,换编码读会怎样(实测)

# 把"中文"按 GBK 编码存成字节
$ printf "中文" | iconv -f UTF-8 -t GBK > gbk.bin

# 按 UTF-8 去读(错配)
$ iconv -f UTF-8 -t UTF-8 gbk.bin
(空 / 乱码)

# 按 GBK 去读(正确)
$ iconv -f GBK -t UTF-8 gbk.bin
中文

这就是乱码的全部原理:字节没坏,读的规则错了。

现代 UTF-8 解码器遇到非法字节序列会丢字符或替换为 �,所以"中文直接消失"也是一种乱码形态——不是没写进去。


4. 四种成因,逐个验

4.1 源文件编码与 IDE 读取编码不一致

现象 判断 修法
打开文件就是乱码(还没运行) 文件本身是 GBK,IDE 按 UTF-8 打开 右下角编码 → Reload in GBK → 再 Convert to UTF-8(先重载再转换,顺序反了会二次损坏)
部分文件乱、部分正常 混用编码 统一转 UTF-8;.editorconfig 加 charset = utf-8(S01-B04 §5.1)

根因:项目里有人用 Windows 记事本(默认 GBK)写了文件,提交时没转码。

4.2 编译期编码

javac 读源文件用的编码由 -encoding 决定。Maven 里对应:

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

症状:源文件里中文正常,编译后 System.out 出现乱码 / 字符串常量在字节码里已损坏。

验证:mvn -X compile 里搜 -encoding,确认是 UTF-8。缺省时取决于平台默认编码——这就是"Maven 在 Windows 上编译中文乱码"的来源。

4.3 运行时默认编码(JVM)

# JDK 8/11 + Windows:显式指定
java -Dfile.encoding=UTF-8 -jar app.jar
# 或在 IDEA 的 VM options / 运行配置里加

判断是不是这一层:只在"运行时输出"乱、源文件和编译都没问题时,就是它。

顺序问题:-Dfile.encoding 必须在启动参数里,程序里 System.setProperty("file.encoding", ...) 在 JDK 18+ 基本无效(字符集在启动时已确定)。

4.4 显示端(IDE 控制台 / 终端 / Windows 代码页)

环境 检查 修法
IDEA 控制台 Settings → Editor → General → Console 的 Default encoding 设为 UTF-8
IDEA 全局 Settings → Editor → File Encodings:Global / Project / Properties 三处都 UTF-8 见 B04
Windows 终端 chcp(936 = GBK,65001 = UTF-8) chcp 65001(仅当前会话);持久化要改终端设置或系统区域
Linux/macOS 终端 locale 里的 LC_ALL/LANG 设 LANG=zh_CN.UTF-8 或 C.UTF-8

关键区分:chcp 65001 只影响当前控制台窗口。新建窗口又变回 936——很多人"改完好了,重启又乱",就是这一条。

4.5 字体(旧文的第一条方案,其实很少是原因)

字体缺失的表现不是"乱码"而是方块/问号。区分方法:

  • 乱码(乱字符)→ 编码问题
  • 方块/豆腐块 → 字体问题(缺中文字形)

旧文把"设置支持中文的字体"列为方案一,优先级应该是最后——它是"显示层缺字形",不是"字节错"。


5. 排障决策树

乱码出现
  │
  ① 还没运行,打开文件就乱?
  │     是 → 源文件编码(§4.1):Reload 正确编码 → Convert to UTF-8
  │     否 ↓
  ② 编译后乱、运行时乱?
  │     是 → 编译期(§4.2):project.build.sourceEncoding / -encoding
  │     否 ↓
  ③ 只在运行/输出时乱?
  │     是 → JVM 默认编码(§4.3):看 java -XshowSettings 的 stdout.encoding
  │     否 ↓
  ④ 只有控制台/终端里乱,文件本身正常?
  │     是 → 显示端(§4.4):IDE Console encoding / chcp / locale
  │     否 ↓
  ⑤ 显示成方块而不是乱字符?
        是 → 字体(§4.5)

每一步都有独立命令可验,不要跳步试。


6. 一套"一次配好"的最小集合

层 配置 放哪
仓库 .editorconfig:charset = utf-8 仓库根(团队共享)
Maven project.build.sourceEncoding=UTF-8 pom.xml
JVM 只在 JDK 8/11 + Windows 时加 -Dfile.encoding=UTF-8 运行配置 / Dockerfile
IDE File Encodings 三处 UTF-8 + Console encoding UTF-8 个人设置(导出进 settings.zip,S01-B09)
终端 Windows Terminal 默认 UTF-8;Linux LANG=*.UTF-8 个人/机器

这五处配好,跨平台协作的乱码问题基本消失。 判据:同一份代码在 Windows 与 Linux 上编译运行,输出一致。


7. 旧文对照

旧文方案(A-1377) 内容 本篇处理
一 设置支持中文的字体 §4.5 降为最后一步,并区分"方块 vs 乱码"
二 File Encodings 三处设 UTF-8 §4.1、§6 保留,补「Reload 再 Convert」的顺序
三 改 IDEA 配置文件加 -Dfile.encoding=UTF-8 §4.3 保留但标注版本条件(JDK 18+ 通常无需)
四 控制台编码设为 UTF-8 §4.4 保留
五 Tomcat 控制台乱码 归入 §4.4(容器日志编码属显示端 + JVM 参数)
— 无诊断顺序、无四层模型 新增 §2–§5:四层模型 + 决策树 + java -XshowSettings 实测

8. 自测

  1. "乱字符"与"方块"分别指向哪一层问题?
  2. JDK 18 之后为什么很多项目不再需要 -Dfile.encoding=UTF-8?那什么时候仍然需要?
  3. Maven 项目里中文乱码,先查 pom.xml 的哪个属性?
  4. chcp 65001 之后为什么"重启又乱了"?
  5. IDEA 里把 GBK 文件"Convert to UTF-8"之前,为什么必须先 Reload in GBK?

9. 下一篇

S01-J07 · 依赖治理:依赖树、冲突、BOM 与私服

编码问题解决后,下一个"看不见的坑"是依赖:同一个库三个版本、传递依赖偷偷降级、私服绕过后编译通过但运行炸。下一篇用 dependency:tree -Dverbose 与 dependency:analyze 把冲突逼到明面上,并给出 BOM 与私服的治理方案。


本篇属于系列《S01 · 开发环境与工具链》。旧文来源:A-1377;扩展点 E-S01-021/022。JVM 编码属性与 GBK 错配为 2026-11-05 实测。

S01-J06 · 编码与字符集排障:一次讲清乱码的四种成因
https://www.clxhxhhr.top/posts/5171/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。