IntelliJ 中的 Java JDK 18 打印问号“?” 当我尝试打印像“\u1699”这样的unicode时

Xin*_*n69 10 java unicode intellij-idea java-18

tldr:我降级到 JDK 17 (17.0.2),现在它可以工作了......

\n

我在 YT 上观看 Kody Simpson 的 Java 初学者教程 (youtube.com/watch?v=t9LP9Nt9Nco),在该教程中,男孩 Kody 打印了名为 Unicode 的疯狂符号,例如“\xe2\x98\xaf\xce\xa9\xc3” \xb8\xe1\x9a\x99",但对我来说它只是打印“?” - 一个问号。

\n
char letter = \'\\u1699\';\nSystem.out.println(letter);\n
Run Code Online (Sandbox Code Playgroud)\n

我尝试了 Stack Overflow 上的几乎所有解决方案,例如:

\n
    \n
  • 将文件编码更改为 UTF-8,尽管我的默认使用 UTF-8。
  • \n
  • 将“-Dconsole.encoding=UTF-8”和“-Dfile.encoding=UTF-8”放入“编辑自定义虚拟机”选项中。
  • \n
  • 弄乱控制面板中的区域设置。
  • \n
\n

这些都不起作用。

\n

每一个帖子都是很多年前的,比如这个,是12年前的:

\n

unicode 字符在 IntelliJ IDEA 控制台中显示为问号

\n

我最终删除并重新下载了 Intellij,因为我认为我弄乱了一些设置并想要重新启动,但这次我将 Project SDK 设置为旧版本,Oracle openJDK 版本 14.0.1,现在它以某种方式工作并打印了 \ '\xe1\x9a\x99\' 符号。

\n

然后我意识到问题可能是 JDK 的最新版本,即版本 18,所以我下载了 JDK 17.0.2,它仍然可以工作并打印出符号 \'\xe1\x9a\x99\',所以那就是好的 :)。但是当我切换回 JDK 版本 18 时,它只打印“?” 再次。

\n

这也很奇怪,因为我可以将 \xe1\x9a\x99 符号复制粘贴到编写代码区域,无论你怎么称呼它,(在 JDK 版本 18 上)

\n
char letter = \'\xe1\x9a\x99\';\nSystem.out.println(letter);\n
Run Code Online (Sandbox Code Playgroud)\n

但是当我按“运行”并尝试打印时......它仍然给出问号。

\n

我不知道为什么会发生这种情况,我开始学习编码 2 天,所以我可能很愚蠢,或者新版本有一个错误,但我从未通过 Google 或这里找到解决方案,所以这就是为什么我发表我的第一篇 Stack Overflow 帖子。

\n

sko*_*isa 15

I can replicate your problem: printing works correctly when running your code if compiled with JDK 17, and fails when running your code if compiled with JDK 18.

\n

One of the changes implemented in Java 18 was JEP 400: UTF-8 by Default. The summary for that JEP stated:

\n
\n

Specify UTF-8 as the default charset of the standard Java APIs. With\nthis change, APIs that depend upon the default charset will behave\nconsistently across all implementations, operating systems, locales,\nand configurations.

\n
\n

That sounds good, except one of the goals of that change was (with my emphasis added):

\n
\n

Standardize on UTF-8 throughout the standard Java APIs, except for\nconsole I/O.

\n
\n

So I think your problem arose because you had ensured that the console\'s encoding in Intellij IDEA was UTF-8, but the PrintStream that you were using to write to that console (i.e. System.out) was not.

\n

The Javadoc for PrintStream states (with my emphasis added):

\n
\n

All characters printed by a PrintStream are converted into bytes using\nthe given encoding or charset, or the default charset if not\nspecified.

\n
\n

Since your PrintStream was System.out, you had not specified any "encoding or charset", and were therefore using the "default charset", which was presumably not UTF-8. So to get your code to work on Java 18, you just need to ensure that your PrintStream is encoding with UTF-8. Here\'s some sample code to show the problem and the solution:

\n
package pkg;\n\nimport java.io.FileDescriptor;\nimport java.io.FileOutputStream;\nimport java.io.PrintStream;\nimport java.nio.charset.StandardCharsets;\n\npublic class Humpty {\n\n    public static void main(String[] args) throws java.io.UnsupportedEncodingException {\n\n        char letter = \'\xe1\x9a\x99\';\n        String charset1 = System.out.charset().displayName();  // charset() requires JDK 18\n\n        System.out.println("Writing the character " + letter + " to a PrintStream with charset " + charset1); // fails\n\n        PrintStream ps = new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8);\n        String charset2 = ps.charset().displayName(); // charset() requires JDK 18\n        ps.println("Writing the character " + letter + " to a PrintStream with charset " + charset2); // works\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是运行该代码时控制台中的输出:

\n
C:\\Java\\jdk-18\\bin\\java.exe -javaagent:C:\\Users\\johndoe\\AppData\\Local\\JetBrains\\Toolbox\\apps\\IDEA-U\\ch-0\\221.5080.93\\lib\\idea_rt.jar=64750:C:\\Users\\johndoe\\AppData\\Local\\JetBrains\\Toolbox\\apps\\IDEA-U\\ch-0\\221.5080.93\\bin -Dfile.encoding=UTF-8 -classpath C:\\Users\\johndoe\\IdeaProjects\\HelloIntellij\\out\\production\\HelloIntellij pkg.Humpty\nWriting the character ? to a PrintStream with charset windows-1252\nWriting the character \xe1\x9a\x99 to a PrintStream with charset UTF-8\n\nProcess finished with exit code 0\n
Run Code Online (Sandbox Code Playgroud)\n

笔记:

\n
    \n
  • PrintStreamJava 18 中charset()有一个新方法,名为“返回此 PrintStream 实例中使用的字符集”。上面的代码调用 charset() ,并显示对于我的机器,我的“默认字符集”windows-1252,而不是UTF-8
  • \n
  • 我使用Intellij IDEA 2022.1 Beta(终极版)进行测试。
  • \n
  • 在控制台中,我使用了DejaVu Sans字体来确保可以渲染字符“\xe1\x9a\x99”。
  • \n
\n
\n

更新:为了解决 Mostafa Zeinali 在下面的评论中提出的问题,可以通过调用将PrintStream使用的System.out重定向到 UTF-8 。这是示例代码:PrintStreamSystem.setOut()

\n
    String charsetOut = System.out.charset().displayName();\n    if (!"UTF-8".equals(charsetOut)) {\n        System.out.println("The charset for System.out is " + charsetOut + ". Changing System.out to use charset UTF-8");\n        System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8));\n        System.out.println("The charset for System.out is now " +    System.out.charset().displayName());\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

这是该代码在我的 Windows 10 计算机上的输出:

\n
The charset for System.out is windows-1252. Changing System.out to use charset UTF-8\nThe charset for System.out is now UTF-8\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,这System.out是一个final变量,因此您不能直接PrintStream为其分配新值。此代码无法编译,并出现错误“无法为最终变量\'out\'赋值”

\n
System.out = new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8); // Won\'t compile\n
Run Code Online (Sandbox Code Playgroud)\n

  • @MostafaZeinali 我不明白你为什么在这里发表这些评论。我的回答具体详细说明了如何通过示例代码解决问题,而不降级到 Java 17,那你为什么要问是否可以呢?该解决方案对您不起作用吗?另外,我的答案甚至没有提到 **-Dfile.encoding=UTF-8** 那么为什么要在这里提出它呢? (2认同)
  • @MostafaZeinali 请参阅 Javadoc 中的“System.setOut()”方法,该方法重新分配“标准”输出流。例如,您可以这样做: `System.setOut(new PrintStream(new FileOutputStream( FileDescriptor.out), true, StandardCharsets.UTF_8));` 到 _“使 System.out 在 java 18 上使用 UTF-8”_ 。这能解决您的担忧吗?如果没有,请考虑发布一个新问题。评论不是进行扩展讨论的地方。 (2认同)
  • @MostafaZeinali 我已经更新了我的答案,以解决您提出的有关如何让“System.out”使用 UTF-8 的问题。 (2认同)

Mos*_*ali 5

TLDR:在 Java 18 上使用它:

\n
-Dfile.encoding="UTF-8" -Dsun.stdout.encoding="UTF-8" -Dsun.stderr.encoding="UTF-8"\n
Run Code Online (Sandbox Code Playgroud)\n

来自JEP 400

\n
\n

JDK 内部使用了三个与字符集相关的系统属性。它们仍然未指定且不受支持,但为了完整性而在此处记录:\nsun.stdout.encoding 和 sun.stderr.encoding \xe2\x80\x94 用于标准输出流 (System.out) 和标准错误的字符集名称流 (System.err) 和 java.io.Console API 中。sun.jnu.encoding \xe2\x80\x94 编码或解码文件名路径(而不是文件内容)时 java.nio.file 的实现所使用的字符集名称。在 macOS 上,其值为“UTF-8”;在其他平台上,它通常是默认字符集。

\n
\n

正如您所看到的,这两个系统属性“仍然未指定且不受支持”。但他们解决了我的问题。因此,请自行承担使用它们的风险,并且不要在生产环境中使用它们。顺便说一句,我正在 Windows 10 上运行 Eclipse。

\n

我认为必须有一个好方法来设置JVM在运行时的默认字符集,并且传递-Dfile.encoding =“UTF-8”并不能做到这一点是愚蠢的。正如您在 JEP 400 中所读到的:

\n
\n

如果 file.encoding 设置为“UTF-8”(即 java -Dfile.encoding=UTF-8),则默认字符集将为 UTF-8。定义此无操作值是为了保留现有命令行的行为。

\n
\n

而这正是它“不”做的事情。传递 Dfile.encoding="UTF-8" 不会“不”保留现有命令行的行为!我认为这表明 Java 18 对 JEP 400 的实现没有做它实际应该做的事情,这首先是问题的根源。

\n

  • 有趣的。我刚刚了解到 Java 19 中添加了两个新的系统属性:**stdout.encoding** 和 **stderr.encoding**。请参阅 [JDK-8285492 发行说明:`System.out` 和 `System.err` 的新系统属性](https://bugs.openjdk.org/browse/JDK-8285492):_"这些属性可以被覆盖启动器的命令行选项(使用“-D”)在需要时将它们设置为“UTF-8”。”_ 这些新属性在 JDK19 Javadoc 中正式定义为 [System.getProperties()](https://docs .oracle.com/en/java/javase/19/docs/api/java.base/java/lang/System.html#getProperties())。 (3认同)
  • [1] 您选择性地引用了 JEP 400,并歪曲了它。其既定目标之一是“在整个标准 Java API 中实现 UTF-8 标准化,**控制台 I/O 除外**”[强调我的]。因此,JEP400 明确不声称可以解决控制台 I/O 的 UTF-8 的任何问题。[2] [Java bug 4163515](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4163515) from 1998 (!!!) 指出:_“file.encoding”属性不是必需的J2SE平台规范...并且不应由用户代码检查或修改_。因此,使用 **-Dfile.encoding=UTF-8** _从未_受到支持。 (2认同)
  • 其实有办法,但我猜你不会很喜欢。从我上面引用的 1998 年的错误报告中可以看出:_“更改虚拟机和运行时系统使用的默认编码的首选方法是在启动 Java 程序之前更改底层平台的区域设置”_。令人惊讶的是,近 24 年后,这显然仍然是“官方”解决方案。Java 的控制台 I/O 有点混乱。 (2认同)