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

Yas*_*jaj 5 java encoding compilation utf-8 character-encoding

我有以下代码

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

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

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

$ javac MainDefault.java
$ java MainDefault
Run Code Online (Sandbox Code Playgroud)

主默认打印

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

$ javac -encoding UTF8 MainDefault.java
$ java MainDefault
Run Code Online (Sandbox Code Playgroud)

MainDefaultUTF8CompilationOnly

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

$ java -Dfile.encoding=UTF8 MainDefault
Run Code Online (Sandbox Code Playgroud)

MainDefaultUTF8CompilationAndRun

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

回声

谢谢你的帮助

jcc*_*ero 8

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

如果要获得相同的字符,首先需要确定哪些字符集到位。

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

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

Oracle提供了Java全面支持的编码信息,并与其他别名的对应-在这种情况下,代码页-在这个页面。

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

正如您在提供的链接中看到的,代码页UTF-865001

如果您使用 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=
Run Code Online (Sandbox Code Playgroud)

知道这些信息后,您需要使用与file.encoding正确字符集对应的VM 选项运行 Java 程序:

java -Dfile.encoding=UTF8 MainDefault
Run Code Online (Sandbox Code Playgroud)

某些类,如PrintStreamPrintWriter,允许您指示Charset将输出信息的位置。

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

如果您将 Windows 与 Git Bash 结合使用,请考虑阅读此@rmunge答案:它提供了有关工具中可能存在的错误的信息,该错误可能是问题的原因,并且会阻止终端在不需要的情况下开箱即用用于手动编码调整。


Tha*_*dra 5

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

这是它的打印方式,

尝试在 Windows 10 上的 Git Bash 中重现它

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

在此处输入图片说明

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

在此处输入图片说明

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

在此处输入图片说明

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

在此处输入图片说明

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


rmu*_*nge 5

简短版本:

\n

通过以下设置可以重现意外行为:

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

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

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

    \n
  • \n
\n

要获得正确的行为:

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

    \n
  • \n
  • 使用 javac -encoding UTF8 编译代码

    \n
  • \n
  • 调用java而不覆盖file.encoding

    \n
  • \n
\n

中号版本:

\n

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

\n

长版本:

\n

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

\n

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

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

那么在上述设置中现在会发生什么?

\n
$ javac MainDefault.java\n$ java MainDefault\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

由于MSYS2 中的错误,GetConsoleCP的本机调用失败。因此回退到返回的字符集cp-1252。但控制台的 OEM 代码页是 cp-850。因此 System.out.println("\xc2\xb2\xc2\xb3") 会产生意外的输出。System.outCharset.defaultCharset()

\n

源代码以UTF-8存储。以 UTF-8 编码“\xc2\xb2\xc2\xb3”需要 4 个字节。但由于缺少-encoding参数,javac 假定每个字符使用一个字节的默认编码。因此它将 4 个字节解释为 4 个字符。String.getBytes使用 1 字节、基于 ANSI 代码页 cp-1252,因此返回 4 个字节。

\n
$ javac -encoding UTF8 MainDefault.java\n$ java MainDefault\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

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

\n
$ java -Dfile.encoding=UTF8 MainDefault\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

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

\n

在此输入图像描述

\n

您的控制台可以打印 \xc2\xb2\xc2\xb3,因为控制台的 8 位 OEM 代码页 (cp-850) 支持这两个字符。System.out但它的编码方式与;-)使用的 ANSI 代码页 cp-1252 略有不同

\n