所以我被我的好友Snoopy狙击了另一个daaaaay ...
他正在欧洲学习CS,并正在为作业编写一个程序,他必须在Windows上输入某些字符并处理它们。程序的相关部分非常简单。就是这样:
Scanner sc = new Scanner(System.in);
String input = sc.next();
for (int i = 0; i < input.length(); i++) {
System.out.print(String.format("%02x", (int)input.charAt(i)));
System.out.println();
}
因此,他运行它并进入一个非ANSI角色:Å(那是U+0161)。他给我的输出是:
>java PrintBytes
š
00
现在很奇怪。我很确定这不是一个无效的角色。我希望看到它的Unicode或UTF-8表示。这是我感到参与的不可控制的冲动。
默认的代码ePASS
我下载了JDK并在我的机器上尝试了。
>java PrintBytes
š
73
好吧,这很奇怪。哦,我的系统编码设置为Windows,而他的系统编码设置为UTF-8。我使用koude0将其更改为65001,即UTF-8,并获得了相同的奇数结果。
从文件重定向输入
下一个测试:如果我从文件中读取相同的输入怎么办?
>java PrintBytes < input.txt
c5
a1
嘿,这是正确的。那就是它的UTF-8表示。因此,与文件输入相比,Java从交互式命令行读取的方式很奇怪,即使两者都通过Stdin。
生锈怎么办?
下一个测试,让我们看看它在Rust中的作用。
use std::io::Read;
fn main() {
for b in std::io::stdin().bytes() {
let val = b.unwrap();
match val {
0xd => println!(""),
0xa => (),
_ => println!("{:#02x}", val),
}
}
}
输出很好:
>target\debug\printbytes.exe
š
0xc5
0xa1
所以Rust在交互作用上做正确的事情。 Rust Code实际上检查了STDIN当前是否是控制台句柄,并调用ReadConsoleW
,否则调用ReadFile
,该ReadFile
处理常规文件I/O都可以。
Snoopy还尝试在Python编写同等程序,这也做对了。因此,Java似乎在某些条件下做错了什么...但是原因是什么?
找到答案
一个好的起点可能是检查Rust source。我的第一个猜测是,在某个地方,我在stdin句柄上看到了对koude2的电话,但是我看到Windows呼叫的最低级别是我不熟悉的功能,koude1。
阅读文档,它引用了有关ANSI兼容性的内容:
ReadConsole
从控制台的输入缓冲区读取键盘输入。它的行为类似于ReadFile
函数,除了它可以在Unicode(宽字符)或ANSI模式中读取。
我找到了另一个链接,该链接给出了a good comparison between koude2 and koude5。它证实ReadConsoleA
(ANSI版本)仅读取ANSI字符,但是ReadConsoleW
可以读取Unicode字符。 Rust正在阅读Unicode字符(希望UTF-16,但我不确定),然后将它们内部转换为UTF-8,因为其字符串类型为entery utf-8。
确认C ++
最简单的确认方法是编写一个小C ++程序,直接通向来源。在不同的模式中,它可以尝试ReadFile
或ReadConsoleW
uint16_t c;
if (argc == 1) {
ReadFile(GetStdHandle(STD_INPUT_HANDLE), reinterpret_cast<uint8_t*>(&c), 1, nullptr, nullptr);
} else {
DWORD numRead;
ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &numRead, nullptr);
}
printf("%04x\n", c);
首先,这里是ReadFile
模式:
>printbytes_c.exe
š
0000
,然后是ReadConsoleW
模式:
>printbytes_c.exe -c
š
0161
u+0161是字符的UTF-16编码,因此似乎显示了一些Unicode支持。有趣的是,ReadConsoleA
表现出与ReadFile
相同的行为。
结论
这种行为在窗户中有些不幸,但似乎有很好的记录。大多数语言似乎在处理此操作方面做得适当,但Java不是。我们甚至可以在调试器中看到它。我没有适当的符号,但至少堆栈的顶部似乎很清楚地解决了。
0:004> k
# Child-SP RetAddr Call Site
00 00000016`03ffce28 00007fff`7157c7f4 KERNEL32!ReadFile
01 00000016`03ffce30 00007fff`7157bd76 java!handleRead+0x20
02 00000016`03ffce70 00007fff`71572641 java!JNI_OnLoad+0x196
03 00000016`03ffef00 00000171`9146a02e java!Java_java_io_FileInputStream_readBytes+0x1d
所以Java ...做得更好。有一种方法可以正确处理Unicode Interactive Console输入。也许是...? Java专家可能会知道,但是我在互联网上找不到任何明显的搜索。但是这个问题是特定于Windows的,所以Windows ...为什么您要这样?总之,计算机不好。