【问题标题】:Java JDK 18 in IntelliJ prints question mark "?" when I tried to print unicode like "\u1699"IntelliJ 中的 Java JDK 18 打印问号“?”当我尝试打印像“\u1699”这样的unicode时
【发布时间】:2022-07-10 22:37:59
【问题描述】:

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

我在 YT (youtube.com/watch?v=t9LP9Nt9Nco) 上观看了 Kody Simpson 的 Java 初学者教程,在该教程中,男孩 Kody 打印了称为 Unicode 的疯狂符号,例如“☯Ωøᚙ”,但对我来说只是印刷 ”?” - 问号。

char letter = '\u1699';
System.out.println(letter);

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

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

都没有用。

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

unicode characters appear as question marks in IntelliJ IDEA console

我最终删除并重新下载了 Intellij,因为我认为我搞砸了一些设置并想要重新启动,但这次我将 Project SDK 设为旧版本,Oracle openJDK 版本 14.0.1,现在不知何故它工作了打印了“ᚙ”符号。

然后我意识到问题可能出在 JDK 的最新版本 18 上,所以我下载了 JDK 17.0.2,它仍然可以正常工作并打印出符号“ᚙ”,这很好:)。但是当我切换回 JDK 版本 18 时,它只会打印“?”再次。

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

char letter = 'ᚙ';
System.out.println(letter);

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

我不知道为什么会发生这种情况,我开始学习编码 2 天所以我可能很笨,或者新版本有错误,但我从来没有通过谷歌或这里找到解决方案,所以这就是我的原因我正在发布我的第一个 Stack Overflow 帖子。

【问题讨论】:

  • 您使用的是哪个平台和哪个终端
  • @BasilBourque 如何导航到 File > Settings...> Editor > General > Console 并查看 Default Encoding 的值?但是,即使这可以为您提供所需的内容,而且我不确定是否可以,如果您可以从状态栏中获取/设置控制台的编码会更方便。
  • @GiacomoCatenazzi 如果控制台字体根本不包含字符'ᚙ',它不会被呈现为替换字符('�')而不是问号('?'), OP看到的是什么?这似乎更有可能是控制台设置问题(编码?)而不是控制台字体问题,因此println(letter) 试图呈现无效/无意义的内容。然而,这些都不能真正解释为什么 OP 可以简单地通过使用 JDK 17 而不是 JDK 18 来解决问题。
  • 对于那些投票结束这个问题的人,因为它是“不可复制或由拼写错误引起的”,你错了。它绝对是可重现的,绝对不是由拼写错误引起的。
  • [1] 请不要将您的解决方案嵌入您的问题中。相反,为您自己的问题创建一个答案。这对 SO 社区更有帮助。 [2] 虽然您回归到 JDK 17 的方法确实可以解决问题,但这并不是问题的解决方案;这只是一种避免解决它的解决方法。只需对 JDK 18 上的代码进行简单更改即可实现适当的修复。

标签: java intellij-idea unicode java-18


【解决方案1】:

我可以复制您的问题:如果使用 JDK 17 编译运行代码时打印可以正常工作,如果使用 JDK 18 编译运行代码时打印会失败。

Java 18 中实现的更改之一是JEP 400: UTF-8 by Default。该 JEP 的摘要指出:

将 UTF-8 指定为标准 Java API 的默认字符集。和 此更改,依赖于默认字符集的 API 将运行 在所有实现、操作系统、语言环境中保持一致, 和配置。

这听起来不错,除了该更改的目标之一是(我强调):

在整个标准 Java API 中标准化 UTF-8,除了 控制台 I/O

所以我认为您的问题出现是因为您确保 Intellij IDEA 中控制台的编码是 UTF-8,但您用来写入该控制台的 PrintStream(即System.out)不是。

Javadoc for PrintStream 状态(我强调):

PrintStream 打印的所有字符都使用以下方法转换为字节 给定的编码或字符集,或默认字符集,如果不是 指定

由于您的PrintStreamSystem.out,因此您没有指定任何“编码或字符集”,因此使用的是“默认字符集”,即大概不是UTF-8。因此,要让您的代码在 Java 18 上运行,您只需确保您的 PrintStream 使用 UTF-8 编码。下面是一些示例代码来显示问题和解决方案:

package pkg;

import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;

public class Humpty {

    public static void main(String[] args) throws java.io.UnsupportedEncodingException {

        char letter = 'ᚙ';
        String charset1 = System.out.charset().displayName();  // charset() requires JDK 18

        System.out.println("Writing the character " + letter + " to a PrintStream with charset " + charset1); // fails

        PrintStream ps = new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8);
        String charset2 = ps.charset().displayName(); // charset() requires JDK 18
        ps.println("Writing the character " + letter + " to a PrintStream with charset " + charset2); // works
    }
}

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

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
Writing the character ? to a PrintStream with charset windows-1252
Writing the character ᚙ to a PrintStream with charset UTF-8

Process finished with exit code 0

注意事项:

  • PrintStream 有一个new method in Java 18 named charset(),它“返回此 PrintStream 实例中使用的字符集”。上面的代码调用了 charset(),并显示对于我的机器,我的 “默认字符集”windows-1252,而不是 UTF-8
  • 我使用 Intellij IDEA 2022.1 Beta(终极版)进行测试。
  • 在控制台中,我使用字体 DejaVu Sans 来确保可以呈现字符“ᚙ”。

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

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

这是我的 Windows 10 机器上该代码的输出:

The charset for System.out is windows-1252. Changing System.out to use charset UTF-8
The charset for System.out is now UTF-8

注意System.out是一个final变量,所以你不能直接给它分配一个新的PrintStream。此代码无法编译并出现错误“无法为最终变量'out'赋值”

System.out = new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8); // Won't compile

【讨论】:

  • 是的,windows 是唯一默认没有 UTF-8 的操作系统。好消息:您可以将其设置为默认值。而且 Powershell 有自己的额外“问题”(非常不同的设计,还有编码(它们是不透明的),所以不要将其视为普通终端,而是将其视为自己的环境)。
  • 问题,不降级到java 17可以解决吗?如果可以,请,我想知道如何!
  • @MostafaZeinali 我不明白你为什么在这里发布这些 cmets。我的回答特别详细说明了如何使用示例代码解决问题,而无需降级到 Java 17,那么您为什么要问这是否可能呢?该解决方案不适合您吗?另外,我的回答甚至没有提到 -Dfile.encoding=UTF-8 那么为什么要在这里提出呢?
  • @MostafaZeinali 请参阅 Javadoc 以了解重新分配“标准”输出流的方法 System.setOut()。例如,您可以这样做:System.setOut(new PrintStream(new FileOutputStream( FileDescriptor.out), true, StandardCharsets.UTF_8));“使 System.out 在 java 18 上使用 UTF-8”。这能解决你的担忧吗?如果没有,请考虑发布一个新问题。评论不是扩展讨论的地方。
  • @MostafaZeinali 我已经更新了我的答案,以解决您提出的关于如何让 System.out 使用 UTF-8 的问题。
【解决方案2】:

TLDR:在 Java 18 上使用它:

-Dfile.encoding="UTF-8" -Dsun.stdout.encoding="UTF-8" -Dsun.stderr.encoding="UTF-8"

来自JEP 400

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

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

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

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

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

【讨论】:

  • [1] 您选择性地引用 JEP 400 并歪曲它。其既定目标之一是“在整个标准 Java API 中标准化 UTF-8,除了控制台 I/O”[强调我的]。因此,JEP400 明确没有声称可以解决控制台 I/O 的 UTF-8 的任何问题。 [2] Java bug 4163515 from 1998 (!!!) 声明:J2SE 平台规范不需要“file.encoding”属性...并且不应由用户代码检查或修改 .所以-Dfile.encoding=UTF-8从来没有得到支持。
  • 嗯.. 有趣。我认为你是对的。但是这里有一个重要的问题:真的没有办法告诉 JVM 用 UTF-8 创建 System.out 和 System.err 吗?这对我来说似乎很奇怪! JEP 400 想要“标准化”,“除了控制台 I/O”。我很清楚这一点。但是,它还旨在“保留现有命令行的行为”。它根本没有!现在,这种行为变化可能是 Eclipse 和 IntelliJ 等 IDE 的错。我认为这是值得研究的事情,但我将把它留给其他人,因为我的问题已经解决了!
  • 其实是有办法的,不过估计你不会很喜欢。从我上面引用的 1998 年的错误报告中:“更改 VM 和运行时系统使用的默认编码的首选方法是在启动 Java 程序之前更改底层平台的语言环境” .令人惊讶的是,近 24 年后,这显然仍然是“官方”解决方案。使用 Java 的控制台 I/O 有点乱。
  • 哇!是的,说实话,我确实搜索过“如何将 Windows 默认字符集更改为 UTF-8”。我发现还有很多不足之处!!!
  • 这应该让您微笑:您链接到的文档 JEP400 最近已更新为:“在 UTF-8 为默认字符集的 JDK 上部署之前,开发人员是强烈建议通过在当前 JDK (8-17) 上使用 java -Dfile.encoding=UTF-8 ... 启动 Java 运行时来检查字符集问题"。因此,他们现在积极敦促开发人员使用与过去相同的 “file.encoding” 设置“不应由用户代码检查或修改”!!!
【解决方案3】:

也遇到过这样的问题。将设置(文件 > 设置... > 编辑器 > 常规 > 控制台)更改为 UTF-32 有助于解决此问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多