UTF-8 不向控制台打印字符

UTF-8 does not print characters to the console

我有以下代码

public class MainDefault {
        public static void main (String[] args) {
                System.out.println("²³");
                System.out.println(Arrays.toString("²³".getBytes()));
        }
}

但似乎无法将特殊字符打印到控制台

当我执行以下操作时,我得到以下结果

$ javac MainDefault.java
$ java MainDefault

另一方面,当我编译它并且运行它像这样

$ javac -encoding UTF8 MainDefault.java
$ java MainDefault

当我运行它使用文件编码 UTF8 标志时,我得到以下内容

$ java -Dfile.encoding=UTF8 MainDefault

这似乎不是控制台的问题(Git Bash on Windows 10),因为它可以正常打印字符

感谢您的帮助

十六进制 C2B2 C2B3,当解释为 UTF-8 时是 ²³

我假设您使用的是 Windows“cmd 终端”?

命令“chcp”控制“代码页”。 chcp 65001 提供 utf8,但它也需要安装一个特殊的字符集。在控制台中设置字体window:Right-click标题上的window→属性→字体→选择Lucida Console

十六进制代码看起来适合 UTF-8。也许您 Git Bash 的字符集不是 UTF-8。对我来说,它看起来像这样:

控制台输出看起来也不错:


2020-09-13 更新: 这是 chcp.com <codepage> 在 Git 中 的证据Bash(薄荷糖)。它没有任何作用。您确实需要 select 在 mintty 设置对话框中输入正确的代码页。


2020-09-15 更新: 好的,在我阅读@rmunge 的回答后,我升级到 Git 2.28 并且可以重现 OP 的问题并且还可以使用 chcp 解决方法(在我的例子中,它没有按照@rmunge 所描述的那样工作)。因为 Git(或 MSYS2,分别)在最新版本中有很多错误,我不想每次打开新的时都从 Git Bash 内部使用 chcp.com控制台,我刚刚降级到我用了3年没有任何问题的版本2.15.1。也许有没有控制台错误的更高版本,我没有尝试,只是使用我计算机上下载文件夹中的旧安装程序。我建议每个人都这样做,现在就解决这个丑陋的错误。使用 non-buggy 控制台版本,它就像我描述的那样工作。

您的代码未在控制台中打印正确的字符,因为您的 Java 程序和控制台使用不同的字符集、不同的编码。

如果要获取相同的字符,首先要确定有哪些字符集。

此过程将取决于您输出结果的“控制台”。

如果您正在使用 Windows 和 cmd,正如@RickJames 所建议的,您可以使用 chcp 命令来确定活动代码页。

Oracle 在 this 页面中提供了 Java 完整支持的编码信息,以及与其他别名(在本例中为代码页)的对应关系。

This Whosebug 答案还提供了一些有关 Windows 代码页和 Java 字符集之间映射的指导。

如您在提供的链接中所见,UTF-8 的代码页为 65001

如果您使用Git Bash (MinTTY),您可以按照@kriegaex 说明验证或配置UTF-8 作为终端模拟器编码。

Linux 和 UNIX,或像 Mac OS 这样的 UNIX 派生系统,不使用代码页标识符,而是使用语言环境。语言环境信息可能因系统而异,但您可以使用 locale 命令或尝试检查 LC_* 系统变量以查找所需信息。

这是我系统中 locale 命令的输出:

LANG="es_ES.UTF-8"
LC_COLLATE="es_ES.UTF-8"
LC_CTYPE="es_ES.UTF-8"
LC_MESSAGES="es_ES.UTF-8"
LC_MONETARY="es_ES.UTF-8"
LC_NUMERIC="es_ES.UTF-8"
LC_TIME="es_ES.UTF-8"
LC_ALL=

了解此信息后,您需要 运行 您的 Java 程序使用与正确字符集相对应的 file.encoding VM 选项:

java -Dfile.encoding=UTF8 MainDefault

有些类,例如PrintStreamPrintWriter,允许您指定Charset输出信息的位置。

-encoding javac 选项只允许您指定源文件使用的字符编码。

如果您将 Windows 与 Git Bash 一起使用,请考虑阅读此 @rmunge :它提供了有关工具中可能存在的错误的信息是问题的原因,它会阻止终端 运行 正确开箱即用,而无需手动编码调整。

我还在 Windows10 上使用 GitBash,它对我来说完全没问题。

这是它的打印方式,

终端版本是 mintty 3.0.2 (x86_64-pc-msys),我的文本属性是,

所以,我尝试通过更改字符集来重现您的输出;

通过将 Character Set 设置为 CP437 (OEM codepage)(请注意,这也会自动将 Locale 更改为 C),我可以获得您所获得的输出。

然后当我将它改回 UTF-8 (Unicode) 后,我可以得到预期的输出!

因此,很明显问题出在您的控制台的字符集上。

在Windows,这与您的代码页有关。 你可以使用命令 chcp 来设置你想要的代码页(例如:如果你想为启动的特定程序设置它)或者你可以在 java 命令行中指定代码页对应的字符集。

如果当前代码页不支持您正在打印的字符,您将在控制台中看到乱码。

不同 shell 的行为可能不同的原因是默认加载的 codepage/charsets。

请查看此 SO post 了解它是如何完成的: System.out character encoding

请验证您的 Windows 10 安装是否启用了 Unicode UTF-8 支持。您可以通过转到“设置”然后查看此选项:“所有设置”->“时间和语言”->“语言”->“管理语言设置”

这是它的样子 - 应该取消选中该功能。

依据:

"²³".getBytes() returns 字符串的编码,基于检测到的默认字符集。在 Windows 10 系统上,默认字符集通常应该是基于 1 字节的编码,与您是从 Windows 控制台还是从 Git [=37 启动 java.exe 无关=].但是您的第一个屏幕截图显示的是 4 字节编码,实际上是 UTF-8。因此,您的 JVM 似乎将 UTF-8 检测为与控制台代码页不兼容的错误默认字符集。

您的控制台可以打印 ²³,因为所使用的代码页支持这两个字符,但编码是基于每个字符一个字节,而 UTF-8 编码要求这两个字符各占 2 个字节。

我对你的第二张截图没有简单的解释,但请注意 Git Bash 基于 MSYS2 which again uses mintty 终端模拟器。虽然 MSYS2 使用 UTF-8,而 mintty 似乎也支持 UTF-8,但整个事情都包含在一个 Windows 控制台中,该控制台基于与 UTF-8 不兼容的 OEM 代码页。整个过程然后在内部使用 UTF-16 的操作系统上运行。现在与否决 OS-level 上的整个 OEM 代码库概念的 Beta 设置相结合,此设置为某些难以理解的行为提供了足够的复杂性。

短版:

使用以下设置可重现意外行为:

  • Windows 10 使用英语、德语或法语,或导致 ANSI 和 OEM 代码页以不同方式编码 ² 和 ³ 的任何其他语言

  • Git for Windows 2.27.0(使用默认设置安装,即 配置为使用 MinTTY 和对伪控制台的实验性支持 禁用)

  • 源代码以 UTF-8 编码存储

要获得正确的行为:

  • re-install Git Windows 2.27.0 并启用实验 在安装程序的最后一页支持伪控制台或 升级到最新的 2.28 版本

  • 使用 javac 编码 UTF8

    编译您的代码
  • 调用 java 而不覆盖 file.encoding

中等版本:

Git for Windows 2.27.0 使用了 MSYS2 that does not set the code page for MinTTY by calling SetConsoleCP when support for pseudo consoles is disabled. The Java runtime determines the codepage for System.out by calling GetConsoleCP. Since no codepage is set when Java is executed within MinTTY terminal, the call fails and Java uses the charset returned by Charset.defaultCharset() as fallback. But in a Windows installation as describe above, Charset.defaultCharset() returns Cp-1252 while the default charset for consoles is Cp-850 的版本。这两个代码页不完全兼容。这导致了奇怪的输出。

长版:

Windows 有两种类型的代码页:ANSI 和 OEM 代码页。第一种类型适用于 UI 不支持 Unicode 的应用程序,后者用于控制台应用程序。两种类型都以 1 字节编码单个字符,但它们并不完全兼容。

因此 Windows Java 必须处理两个字符集而不是一个:

  • Charset.defaultCharset() returns ANSI 代码页(通常是 cp-1252)。此字符集由 file.encoding 系统 属性 指定。如果未指定为 VM 参数,java 可执行文件会确定 ANSI 代码页并在初始化期间添加系统 属性。 String.getBytes() 使用 Charset.defaultCharset() 返回的字符集。
  • System.out 使用控制台的 OEM 代码页(通常是 cp-850)。 java 可执行文件通过调用 GetConsoleCP function and sets the it as value for the internal system properties, sun.stdout.encoding and sun.stdout.encoding. When the call to GetConsoleCP fails the charset returned by Charset.defaultCharset() is used. This only happens when the console in which java.exe is executed hasn't set the OEM codepage before, by calling SetConsoleCP
  • 获取此代码页

那么上面提到的设置现在发生了什么?

$ javac MainDefault.java
$ java MainDefault

GetConsoleCP fails due to the bug in MSYS2的原生调用。因此 System.out 退回到 Charset.defaultCharset() 返回的字符集,即 cp-1252。但是控制台的 OEM 代码页是 cp-850。因此 System.out.println("²³") 会产生意外的输出。

源代码以UTF-8格式存储。在 UTF-8 中编码“²³”需要 4 个字节。但是由于缺少 -encoding 参数 javac 假定默认编码每个字符使用一个字节。因此它将 4 个字节解释为 4 个字符。 String.getBytes 使用基于 ANSI 代码页的 1 字节 cp-1252,因此 returns 4 个字节。

$ javac -encoding UTF8 MainDefault.java
$ java MainDefault

使用 -编码 UTF8 参数 javac 将 UTF-8 编码源解释为 UTF-8。所以“²³”的 4 个字节被正确识别为两个字符。 System.out 将 cp-1252 中的两个字符编码为 2 个字节。但由于控制台仍使用 cp-850,输出仍然损坏。 String.getBytes 也在 cp-1252 中对 wo 字符进行编码,这导致 2 个字节。

$ java -Dfile.encoding=UTF8 MainDefault

系统 属性、file.encoding 覆盖 Charset.defaultCharset() 返回的字符集,String.getBytes() 也使用该字符集。最初被 javac 错误解释为 8 位编码中的 4 个字符的两个字符现在以 UTF-8 正确编码为两个字符,每个字符以两个字节编码。这导致 4 个字节。由于 file.encodingSystem.out 使用的字符集没有任何影响 4(而不是 2,由于 java 的错误解释c) 字符仍然在 cp-1252 中编码,控制台仍然使用 cp-850,你仍然得到一个损坏的输出。

您的控制台可以打印 ²³,因为控制台的 8 位 OEM 代码页 (cp-850) 支持这两个字符。但它的编码与 System.out ;-)

使用的 ANSI 代码页 cp-1252 略有不同

我在 Windows git bash 中遇到了同样的问题。 javajavac 无法正确打印汉字。将 git-bash 的字符集设置为 UTF8 没有帮助。 chcp 也不行。从 git bash 的安装向导中,我知道像 python 这样的程序没有 winpty 就无法正常工作。我已将 alias python='winpty python 添加到 ~/.bashrc。所以我尝试了 winpty java Foo.javawinpty javac Foo.java,幸运的是问题已经解决了。我将别名添加到 ~/.bashrc 以解决问题:

alias java='winpty java'
alias javac='wintpy javac'

git bash for Windows 的最新版本 (v2.2x) 包含了一个关于 winpty 的实验性功能,但似乎仍然存在一些问题, 所以我一直保留着这些别名。