问题描述
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
控制台(在Windows 10上为Git Bash)似乎没有问题,因为它可以正常打印字符
感谢您的帮助
解决方法
由于您的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
某些类,例如PrintStream
或PrintWriter
,可让您指示将在其中输出信息的Charset
。
-encoding
javac
选项仅允许您指定源文件使用的字符编码。
如果您将Windows与Git Bash一起使用,请考虑同时阅读此@rmunge answer:它提供了有关该工具中可能存在的错误的信息,这可能是问题的原因,并且会阻止终端正常运行。开箱即用,无需手动进行编码调整。
,我也在Windows 10上使用 Git Bash ,它对我来说完全正常。
这里是打印方式,
终端版本为mintty 3.0.2 (x86_64-pc-msys)
,“我的文本”属性为
因此,我尝试通过更改字符集来重现您的输出;
通过将“字符集”设置为CP437 (OEM codepage)
(请注意,这也会自动将“语言环境”更改为C
),我可以按需获取输出。
然后将其更改回UTF-8 (Unicode)
之后,我可以按预期获得输出!
因此,很明显问题出在控制台的字符集上。
,对于UTF-8,十六进制代码看起来还可以。也许您为Git Bash设置的字符集不是UTF-8。对我来说看起来像这样:
控制台输出看起来也不错:
更新2020-09-13::这证明chcp.com <codepage>
在Git Bash(极小)中不能正常运行。它没有任何作用。您确实必须在薄荷设置对话框中选择正确的代码页。
更新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
由于GetConsoleCP,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
使用基于1字节的ANSI代码页cp-1252,因此返回4个字节。
$ javac -encoding UTF8 MainDefault.java
$ java MainDefault
使用 -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
系统属性 file.encoding 覆盖Charset.defaultCharset()
返回的字符集,该字符集也被String.getBytes()
使用。这两个字符最初被javac错误地解释为8位编码中的4个字符,现在已以UTF-8正确编码为两个字符,每个字符两个字节编码。这导致4个字节。由于 file.encoding 对System.out
使用的字符集没有任何影响,因此4个(而不是2个,因为对Javac的解释有误)仍在cp-1252中编码,控制台仍使用cp-850,并且输出仍然损坏。
由于控制台的8位OEM代码页(cp-850)支持两个字符,因此您的控制台可以打印“³”。但它的编码方式与System.out
;-)
在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支持。您可以通过依次转到“设置”和“所有设置”->“时间和语言”->“语言”->“管理语言设置”来查看此选项
这是它的外观-应该取消选中该功能。
理论价格:
"²³".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 中遇到了同样的问题。 java
和 javac
无法正确打印汉字。将 git-bash 的字符集设置为 UTF8 没有帮助。 chcp
也不起作用。从 git bash 的安装向导中,我知道 python
之类的程序在没有 winpty
的情况下无法正常工作。我已将 alias python='winpty python
添加到 ~/.bashrc
。所以我尝试了 winpty java Foo.java
和 winpty javac Foo.java
,幸运的是问题消失了。我将别名添加到 ~/.bashrc
以解决问题:
alias java='winpty java'
alias javac='wintpy javac'
最近的 git bash for Windows 版本 (v2.2x) 包含了一个关于 winpty
的实验性功能,但它似乎仍然存在一些问题,所以到目前为止我保留了这些别名。