Java控制台应用程序中的Unicode输入

5

我一直在尝试在我的Java应用程序中检索“unicode用户输入”以获得一个小型实用程序片段。问题是,在Ubuntu上似乎“开箱即用”,这个操作系统似乎使用UTF-8进行编码,但当从Windows的“cmd”运行时却无法正常工作。需要考虑的代码如下:

public class SerTest {

    public static void main(String[] args) throws Exception {
        testUnicode();
    }

    public static void testUnicode() throws Exception {
        System.out.println("Default charset: " +
           Charset.defaultCharset().name());
        BufferedReader in  =
           new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
        System.out.printf("Enter 'абвгд эюя': ");
        String line = in.readLine();
        String s = "абвгд эюя";
        byte[] sBytes = s.getBytes();
        System.out.println("strg bytes: " + Arrays.toString(sBytes));
        byte[] lineBytes = line.getBytes();
        System.out.println("line bytes: " + Arrays.toString(lineBytes));
        PrintStream out = new PrintStream(System.out, true, "UTF-8");
        out.print("--->" + s + "<----\n");
        out.print("--->" + line + "<----\n");
    }

}

在Ubuntu上的输出(无需更改配置):

me@host> javac SerTest.java  && java SerTest
Default charset: UTF-8
Enter 'абвгд эюя': абвгд эюя
strg bytes: [-48, -80, -48, -79, -48, -78, -48, -77, -48, -76, 32, -47, -115, -47, -114, -47, -113]
line bytes: [-48, -80, -48, -79, -48, -78, -48, -77, -48, -76, 32, -47, -115, -47, -114, -47, -113]
--->абвгд эюя<----
--->абвгд эюя<----

在Windows CMD提示符上的输出(不受JAVA_TOOL_OPTIONS影响):

E:\>chcp 65001
Active code page: 65001

E:\>java -Dfile.encoding=utf8 SerTest
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=utf8
Default charset: UTF-8
Enter 'абвгд эюя': юя': ': абвгд эюя
strg bytes: [-48, -80, -48, -79, -48, -78, -48, -77, -48, -76, 32, -47, -115, -47, -114, -47, -113]
Exception in thread "main" java.lang.NullPointerException
        at SerTest.testUnicode(SerTest.java:26) # byte[] lineBytes = line.getBytes();
        at SerTest.main(SerTest.java:15)

在使用JAVA_TOOL_OPTIONS后,在Eclipse控制台中的输出:
Default charset: UTF-8
Enter 'абвгд эюя': абвгд эюя
strg bytes: [-48, -80, -48, -79, -48, -78, -48, -77, -48, -76, 32, -47, -115, -47, -114, -47, -113]
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=utf8
line bytes: [-48, -80, -48, -79, -48, -78, -48, -77, -48, -76, 32, -47, -115, -47, -114, -47, -113]
--->абвгд эюя<----
--->абвгд эюя<----

在Eclipse控制台中,它可以工作,因为我添加了一个系统范围的环境变量(JAVA_TOOL_OPTIONS),如果可能的话,我想避免使用该变量。
Eclipse控制台输出(删除JAVA_TOOL_OPTIONS之后):
Default charset: UTF-8
Enter 'абвгд эюя': абвгд эюя
strg bytes: [-48, -80, -48, -79, -48, -78, -48, -77, -48, -76, 32, -47, -115, -47, -114, -47, -113]
line bytes: [-61, -112, -62, -80, -61, -112, -62, -79, -61, -112, -62, -78, -61, -112, -62, -77, -61, -112, -62, -76, 32, -61, -111, -17, -65, -67, -61, -111, -59, -67, -61, -111, -17, -65, -67]
--->абвгд эюя<----
--->абвгд �ю�<----

所以我的问题是:这里到底发生了什么?为了确保此代码片段适用于各种“Unicode”输入,需要进行哪些代码更改?

抱歉问题有点冗长,提前感谢您的帮助。
Sasuke

2个回答

4

一些注释:

  • -Dfile.encoding=utf8 不受支持,可能会导致意外的副作用:

"file.encoding"属性不是J2SE平台规范所必需的;它是Sun实现的内部细节,不应该被用户代码检查或修改。它也旨在只读;在命令行或程序执行期间的任何其他时间将此属性设置为任意值在技术上是不可能支持的。

  • Console类将检测并使用终端编码,但在Windows上不支持65001(UTF-8)-至少在我上次尝试时没有支持
我认为在cmd.exe中正确且有文档记录的使用Unicode的方法是使用WriteConsoleWReadConsoleW
当我研究这个问题时,我写了几篇博客文章:

1
啊,所以基本上在编写 Windows 命令行应用程序时没有明智的方式来读取/写入 Unicode 内容?而我正在调试 sun.* 包中的 UTFEncoder/Decoder... - sasuke
据我所知,目前没有跨平台的方法。有许多第三方控制台库可以为所有平台提供通用接口,但我不知道它们具有多少国际化支持。 - McDowell
谢谢。我想我得研究一下目前流传的几个 curses 实现(比如这个:http://slashie.net/libjcsi/),并希望它们以合理的方式处理 Unicode。已接受! - sasuke

3
当你尝试调用Arrays.toString(lineBytes)时会抛出NPE,这意味着lineBytes是空的。 lineBytes保存值:line.getBytes()。只有在内部抛出UnsupportedEncodingException时,getBytes()才会返回null。
这在Windows上发生是因为Windows命令提示符默认不支持unicode。这在Ubuntu上可以正常工作,因为它的命令提示符完全支持unicode。在Eclipse上部分工作是因为Eclipse的控制台窗口是一个支持输入和输出unicode字符的Java组件,并使用JAVA_TOOL_OPTIONS进行输出。
归根结底,您希望配置Windows命令提示符以能够使用unicode字符。我看到了几个关于此主题的讨论,请查看Unicode characters in Windows command line - how? 我希望这可以帮助您。

这就是正确的方式。我认为没有人能够对这个答案进行任何补充。 - Milad Naseri
感谢回复。有几个澄清:NPE是因为在line上调用getBytes(),这意味着line为空,这没有太多意义。我可以确认没有抛出UnsupportedEncodingException(至少我没有看到)。最后,我尝试了链接线程中提到的建议,结果相同。你有什么想法可能出了问题吗? - sasuke
@sasuke,我认为你是错的。看看你的堆栈跟踪:在SerTest.testUnicode(SerTest.java:26)行.getBytes(); 在SerTest.main(SerTest.java:15)处,这意味着main()和抛出NPE的地方之间有11行。而这正是byte[] lineBytes = line.getBytes(); - AlexR
嗨,Alex,我可以告诉你是 line.getBytes(),因为我添加了一个新行 System.out.println(line),它给了我 null。另外,如果你在Windows上,我会很感激如果你能运行相同的代码并让我知道它是否适用于你。谢谢。 - sasuke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接