Java错误地读取System.in中的重音字符



如果您面临同样的问题,并且您的字符集被ANSI测试编码覆盖(代码页1252或ISO 8859-1),您可以使用该编码代替临时规避UTF-8的问题,但是UTF-8是包含最终本地化的所有脚本的现代标准。

我正在创建一个应用程序,它必须从控制台读取包含重音字符的用户输入。从我在网上看到的内容来看,现代控制台能够处理重音字符输出,并正确编码输入,即使它们在发送命令之前显示为?

PS C:> echo ?
ü
Ps C:>

注意:此行为在命令提示符中不可重现。命令提示符,当在Windows终端中运行时,似乎在发送之前正确显示重音字符。

但是,当运行以下测试代码时:

package com.test.outputtest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.nio.file.*;
public class OutputTest {
public static void main(String[] args) {
// Set I/O to use UTF-8
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8));
// Create the response listener
Scanner input = new Scanner(System.in, StandardCharsets.UTF_8);
System.out.println(Arrays.toString("èéëê".getBytes(StandardCharsets.UTF_8)));
String temp = input.nextLine();
System.out.println(Arrays.toString(temp.getBytes(StandardCharsets.UTF_8)));
}
}

这是输出(在构建工件"app.jar"之后):

PS C:Users[name]Desktopoutput_test> chcp 65001
Active code page: 65001
PS C:Users[name]Desktopoutput_test> java "-Dfile.encoding=UTF-8" -jar app.jar
[-61, -88, -61, -87, -61, -85, -61, -86]
èéëê
[0, 0, 0, 0]

第一个字节数组来自预写字符串,第二个数组是输入字符串的字节。echo正确输出重音的事实使我相信这是一个编译器错误,但我不确定如何修复它。我试过用Console代替Scanner,这给了我同样的错误。

在IntelliJ内部运行时,在终端输入ü时读取完全正常。这也是我怀疑编译过程中出现问题的原因。当使用命令提示符而不是PowerShell运行时,会出现相同的错误。

注意:我使用Windows终端运行PowerShell和使用IntelliJ Idea社区版2021.3。除了工件构建文件路径和其他一些特定于项目的文件路径之外,我还没有编辑.xml文件。

  • 操作系统:Windows 10 build 19045.2728
  • Java版本:17.0.6(也在IntelliJ)
  • 默认代码页:850 (OEM)
  • 发生错误的代码页:65001 (UTF-8)

我可以重现你的问题,但我看不出你的代码有什么问题,我没有简单的解决方案。令人难以置信的是,似乎即使使用最新版本的Java(18,19,20),从Windows控制台读取UTF-8字符仍然存在问题。

这在JDK bug JDK-8295672中有正式的文档记录。这是公开的,未解决的。它说(加上我的强调):

读取System.in是有问题的,因为它是编码的输入流主机的编码。对于JEP 400,在某些情况下默认编码(UTF-8)和主机的本机编码不同. 阅读对于正确的字节,用户必须转换字节原生到默认,这似乎是一个基本使用的障碍。提供更好的访问方式(无需考虑编码内容)

所以将默认字符集设置为UTF-8并不能解决问题,因为主机的本机编码不是UTF-8,并且对此您无能为力(至少对于Windows上的cmd.exePowerShell)。

指出:

  • 我的理解是,这只是一个问题在Windows上。Linux和Mac处理UTF-8输入没有问题。
  • 一个可能的解决方案是使用JNA (Java Native Access)方法来读取控制台输入而不是使用Scanner。请参阅如何使用Java Native Access从打开的Windows控制台(命令提示符)读取内容来帮助您开始。另请参阅Javadoc,了解JNA的WinCon接口,特别是ReadConsoleInput()
  • 虽然它不能解决您的问题,但您可以考虑升级到最新版本的Java(18,19或20),因为Java 18中默认实现了JEP 400: UTF-8。这是JEP400的目标之一(加上我的强调):

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

  • 可能是由于上面提到的"主机编码">问题,JEP400中排除了控制台I/O。
  • 一个明显的问题是,为什么你的代码工作时运行Intellij?我怀疑这是因为JetBrains使用JNA从他们的控制台读取输入,但这只是一个猜测。

相关内容

最新更新