You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

当标准输出(stdout)被重定向时,如何获取System.err的正确字符集?

关于Java中System.out/System.err字符集的困惑与问题

确定System.out的字符集确实是个棘手的问题,相关讨论甚至影响到了Logback这类日志框架(比如涉及System.err输出编码错误的场景)。先来看官方API文档的说明:

“标准”输出流,该流已打开并准备好接收输出数据。通常此流对应显示输出,或由宿主环境或用户指定的其他输出目标。从字符转换为字节时使用的编码,若存在Console则等效于Console.charset(),否则为Charset.defaultCharset()。

在我的Windows 10命令提示符(Java 17环境)里做了测试:Charset.defaultCharset()返回windows-1252,但System.console().charset()返回IBM437。如果用new OutputStreamWriter(System.out, System.console().charset())写入字符串"é",能正常输出é;但要是用new OutputStreamWriter(System.out, Charset.defaultCharset())写入同一个字符,输出的却是Θ——这就是为什么必须匹配Console的字符集才能得到正确结果的原因。

但这里有个绕不开的坑:当stdout被重定向的时候(比如命令行里用… > temp.out),System.console()会返回null,这时候就得改用Charset.defaultCharset()。基于这个逻辑,我们可以用下面这段代码来确定System.out的字符集(代码不算最优,但能清晰说明逻辑):

final Charset systemOutCharset = System.console() != null ? System.console().charset() : Charset.defaultCharset();

本以为System.err会遵循同样的规则——有Console就用System.console().charset(),没有就用Charset.defaultCharset(),API文档里也明确写了同样的转换规则。但实际测试发现一个严重问题:如果只重定向stdout而不重定向stderr,在我的系统里,被重定向的System.out会用windows-1252写入,但System.err还是会用IBM437输出到控制台。这完全违反了API约定啊!因为此时Console已经不存在了,按道理System.err应该用Charset.defaultCharset(),可它却还在用原本Console存在时的字符集。

更头疼的是:这时候我们根本没法获取System.err的正确字符集——因为stdout被重定向导致System.console()为null了!那这种情况下,怎么才能拿到System.err该用的字符集?为什么它不遵守API约定?

我只能猜测这是Java API的一个疏漏,应该提供一个类似System.getConsoleCharset()的方法,不管System.console()是否存在,都能返回正确的控制台字符集。

这个问题的影响比想象中要大。像Logback这类日志框架,通常会被配置成把日志输出到stderr(这也是很多场景下的最佳实践),而这些日志包为了兼容跨版本,未来很多年都不会强制要求依赖Java 18——毕竟它们是做跨切面功能的,必须兼容最低支持的Java版本。

正因为Java的这个缺陷(既是功能缺陷,也是API设计上的盲点),当用stderr输出日志时,日志系统根本没法确切知道该用哪种字符集!

内容的提问来源于stack exchange,提问作者Garret Wilson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:33:10