JDK 26 中 System.out.println() 中文乱码的排查与解决
作者:Johnson_112358
1. 引言
最近在将一个项目升级到 JDK 26 进行 API 大模型接入测试时,遇到了一个奇怪的问题:在 JUnit 测试中,使用 System.out.println() 打印的中文全部变成了方块乱码,而使用 Logback 等日志框架打印的中文却能正常显示。更令人困惑的是,同样的代码在旧版 JDK(如 JDK 11)项目中运行完全正常。
经过一番排查,发现这是 JDK 18+ 引入的一个关于标准输出流编码的底层变更所致。本文将详细记录这个问题的现象、根因以及最终的解决方案。
2. 问题现象
在 JDK 26 项目中,执行以下 JUnit 测试:
public class ApiTest {
@Test
void testChineseOutput() {
System.out.println("你好,世界!");
System.out.println("测试中文乱码问题");
}
}
控制台输出结果为:
????????
????????
所有中文字符均显示为方块乱码。但与此同时,项目中配置的 Logback 日志输出却是正常的:
2026-05-28 14:55:06 [main] INFO c.e.ApiTest - 你好,世界!
2026-05-28 14:55:06 [main] INFO c.e.ApiTest - 测试中文乱码问题
3. 根因分析:JDK 18+ 的 stdout.encoding 变更
3.1 历史行为(JDK 17 及之前)
在 JDK 17 及更早版本中,System.out 的编码完全由 file.encoding 系统属性决定。因此,只要在启动参数中设置 -Dfile.encoding=UTF-8,System.out.println() 就会使用 UTF-8 编码输出,中文显示正常。
3.2 新行为(JDK 18+)
从 JDK 18 开始,为了解耦标准输出流与文件系统的编码,JDK 引入了一个新的、独立的系统属性:stdout.encoding。
- stdout.encoding:专门用于控制 System.out 和 System.err 的编码。
- 默认值:stdout.encoding 的默认值等于 native.encoding,即操作系统的本地编码。在中文 Windows 系统上,native.encoding 通常是 GBK。
这意味着,即使你通过 -Dfile.encoding=UTF-8 强制设置了文件编码,System.out.println() 仍然会使用 stdout.encoding(即 GBK)进行输出。如果 IDE 的控制台或终端期望接收 UTF-8 编码的字节流,就会因为编码不匹配而出现乱码。
3.3 为什么 Logback 不乱码?
Logback 等日志框架(以及 Log4j2)的 ConsoleAppender 拥有独立的编码配置。它们不依赖 System.out 的编码,而是通过自己的 encoder 和 charset 设置来直接控制输出到控制台的字节流编码。例如,在 logback.xml 中:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 这里明确指定了编码 -->
</encoder>
</appender>因此,日志框架不受 stdout.encoding 的影响,可以正常输出中文。
4. 解决方案
要解决 System.out.println() 的中文乱码问题,有以下几种方案:
方案一:设置 stdout.encoding 系统属性(推荐)
最直接的方法是在 JVM 启动参数中,与 file.encoding 一起显式设置 stdout.encoding。
在 IDE(如 IntelliJ IDEA)中:
- 打开 Run -> Edit Configurations...。
- 在
VM options中添加:-Dfile.encoding=UTF-8 -Dstdout.encoding=UTF-8
在 Maven Surefire 插件中(用于 JUnit 测试):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>-Dfile.encoding=UTF-8 -Dstdout.encoding=UTF-8</argLine>
</configuration>
</plugin>方案二:在代码中手动指定编码
如果无法修改 JVM 参数,可以在代码中创建一个使用指定编码的 PrintStream 来替换 System.out。
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws Exception {
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8.name()));
System.out.println("你好,世界!"); // 现在可以正常输出了
}
}
方案三:使用日志框架替代 System.out
从工程实践角度,建议在正式代码中始终使用日志框架(如 Logback、Log4j2)来替代 System.out.println()。这不仅能避免编码问题,还能提供更丰富的日志级别、格式化和输出控制。
5. 总结
JDK 18+ 引入的 stdout.encoding 属性是导致新版 JDK 中 System.out.println() 中文乱码的根本原因。它独立于 file.encoding,默认跟随系统本地编码。
解决此问题的最佳实践是:
- 在 JVM 启动参数中同时设置 -Dfile.encoding=UTF-8 和 -Dstdout.encoding=UTF-8。
- 优先使用日志框架进行输出,以获得更好的编码控制和工程规范性。
希望这篇文章能帮助遇到同样问题的开发者快速定位并解决 JDK 新版本中的中文乱码问题。
到此这篇关于JDK 26 中 System.out.println() 中文乱码的排查与解决的文章就介绍到这了,更多相关JDK26 System.out.println() 中文乱码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
