UTF-8不会将字符打印到控制台

问题描述

我有以下代码

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

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

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

$ javac MainDefault.java
$ java MainDefault

MainDefaultPrinting

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

$ javac -encoding UTF8 MainDefault.java
$ java MainDefault

MainDefaultUTF8CompilationOnly

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

$ java -Dfile.encoding=UTF8 MainDefault

MainDefaultUTF8CompilationAndRun

控制台(在Windows 10上为Git Bash)似乎没有问题,因为它可以正常打印字符

Echo

感谢您的帮助

解决方法

由于您的Java程序和控制台使用不同的字符集和不同的编码,因此您的代码未在控制台中显示正确的字符。

如果要获取相同的字符,则首先需要确定使用哪些字符集。

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

如果您使用的是Windows和cmd(如@RickJames建议),则可以使用chcp命令确定活动代码页。

Oracle在this页面中提供了Java完全支持的编码信息以及与其他别名(在这种情况下为代码页)的对应关系。

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

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

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

Linux和UNIX或UNIX派生的系统(如Mac OS)不使用代码页标识符,而使用语言环境。区域设置信息在不同系统之间可能会有所不同,但是您可以使用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=

一旦知道了这些信息,就需要使用file.encoding VM选项(对应于正确的字符集)来运行Java程序:

java -Dfile.encoding=UTF8 MainDefault

某些类,例如PrintStreamPrintWriter,可让您指示将在其中输出信息的Charset

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

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

,

我也在Windows 10上使用 Git Bash ,它对我来说完全正常。

这里是打印方式,

Trying to reproduce it in Git Bash on Windows 10

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

enter image description here

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

enter image description here

通过将“字符集”设置为CP437 (OEM codepage)(请注意,这也会自动将“语言环境”更改为C),我可以按需获取输出。

enter image description here

然后将其更改回UTF-8 (Unicode)之后,我可以按预期获得输出!

enter image description here

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

,

对于UTF-8,十六进制代码看起来还可以。也许您为Git Bash设置的字符集不是UTF-8。对我来说看起来像这样:

Text and font settings for mintty (Git Bash)

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

Console output UTF-8


更新2020-09-13::这证​​明chcp.com <codepage>在Git Bash(极小)中不能正常运行。它没有任何作用。您确实必须在薄荷设置对话框中选择正确的代码页。

screen recording of Git Bash mintty


更新2020-09-15:好吧,当我阅读@rmunge的答案后,我升级到Git 2.28,可以重现OP的问题,也可以使用chcp解决方法(它没有按照@rmunge的说明进行工作)。由于Git(或分别为MSYS2)在最新版本中是如此麻烦,并且我不想每次打开新控制台时都从Git Bash内部使用chcp.com,所以我将版本降级为2.15.1已经使用了三年没有任何问题。也许有更高版本没有控制台错误,我没有尝试,只是使用计算机上downloads文件夹中的旧安装程序。我建议每个人都做同样的事情,现在解决这个丑陋的错误。在非越野车控制台版本中,它的工作方式就像我描述的一样。

,

简短版本:

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

  • 使用英语,德语或法语或其他导致ANSI和OEM代码页对²和³进行不同编码的其他语言的Windows 10

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

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

要获得正确的行为,

  • 重新安装Windows 2.27.0的Git并启用实验性功能 在安装程序的最后一页上支持伪控制台,或者 升级到最新的2.28版本

  • 使用javac -encoding UTF8编译代码

  • 调用Java而不覆盖file.encoding

中型版本:

Windows 2.27.0的Git使用的MSYS2版本在禁用伪控制台支持时不会通过调用SetConsoleCP来设置MinTTY的代码页。 Java运行时通过调用GetConsoleCP来确定System.out的代码页。由于在MinTTY终端中执行Java时未设置任何代码页,因此调用失败,并且Java使用Charset.defaultCharset()返回的字符集作为后备。但是,在如上所述的Windows安装中,Charset.defaultCharset()返回Cp-1252,而控制台的默认字符集为Cp-850。这两个代码页不完全兼容。这会导致奇怪的输出。

长版:

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

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

  • Charset.defaultCharset()返回ANSI代码页(通常为cp-1252)。此字符集由 file.encoding 系统属性指定。如果未将其指定为VM参数,则Java可执行文件将确定ANSI代码页并在初始化期间添加系统属性。 String.getBytes()使用Charset.defaultCharset()返回的字符集。
  • System.out对控制台使用OEM代码页(通常为cp-850)。 Java可执行文件通过调用GetConsoleCP函数来获取此代码页,并将其设置为内部系统属性 sun.stdout.encoding sun.stdout.encoding 的值em>。当对GetConsoleCP的调用失败时,将使用Charset.defaultCharset()返回的字符集。仅当执行java.exe的控制台之前未通过调用SetConsoleCP
  • 设置OEM代码页时,才会发生这种情况

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

$ javac MainDefault.java
$ java MainDefault

enter image description here

由于GetConsoleCPbug 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使用基于1字节的ANSI代码页cp-1252,因此返回4个字节。

$ javac -encoding UTF8 MainDefault.java
$ java MainDefault

enter image description here

使用 -encoding 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

enter image description here

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

enter image description here

由于控制台的8位OEM代码页(cp-850)支持两个字符,因此您的控制台可以打印“³”。但它的编码方式与System.out;-)

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

在Windows上,它与您的代码页有关。 您可以使用命令chcp来设置所需的代码页(例如:如果要为启动的特定程序进行设置),也可以在Java命令行中指定与代码页相对应的字符集。

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

不同的shell可能表现不同的原因是由于默认加载的代码页/字符集。

请查看此SO帖子以了解操作方法: System.out character encoding

,

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

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

命令“ chcp”控制“代码页”。 chcp 65001提供utf8,但也需要安装特殊的字符集。要在控制台窗口中设置字体,请执行以下操作:右键单击窗口标题→属性→字体→选择Lucida Console

,

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

这是它的外观-应该取消选中该功能。

enter image description here

理论价格:

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

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

对于第二个屏幕截图,我没有简单的解释,但是请注意,Git Bash基于MSYS2,它再次使用mintty终端模拟器。虽然MSYS2使用UTF-8,并且mintty也似乎支持UTF-8,但整个组件都包装在Windows控制台中,该控制台基于与UTF-8不兼容的OEM代码页。整个过程然后在内部使用UTF-16的操作系统上运行。现在,结合了Beta设置,该设置推翻了OS级整个OEM代码库的概念,此设置为某些难以理解的行为提供了足够的复杂性。

,

我在 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 的实验性功能,但它似乎仍然存在一些问题,所以到目前为止我保留了这些别名。