是的,这是令人沮丧的有时候type和其他程序会打印乱码,有时候则不会。 首先,Unicode字符将只显示if the current console font contains the characters。所以使用像Lucida Console这样的TrueType字体,而不是默认的光栅字体。 但是如果控制台字体不包含你想要显示的字符,你会看到问号而不是乱码,当你看到乱码时,不仅仅是字体设置。 当程序使用标准C-库I/O函数如printf时,程序的输出编码必须与控制台的输出编码匹配,否则会出现乱码。chcp显示并设置当前代码页。所有使用标准C-库I/O函数的输出都被视为chcp显示的代码页。 将程序的输出编码与控制台的输出编码进行匹配可以通过两种不同的方式完成:
import java.io.*;
public class Foo {
private static final String BOM = "\ufeff";
private static final String TEST_STRING
= "ASCII abcde xyz\n"
+ "German äöü ÄÖÜ ß\n"
+ "Polish ąęźżńł\n"
+ "Russian абвгдеж эюя\n"
+ "CJK 你好\n";
public static void main(String[] args)
throws Exception
{
String[] encodings = new String[] {
"UTF-8", "UTF-16LE", "UTF-16BE", "UTF-32LE", "UTF-32BE" };
for (String encoding: encodings) {
System.out.println("== " + encoding);
for (boolean writeBom: new Boolean[] {false, true}) {
System.out.println(writeBom ? "= bom" : "= no bom");
String output = (writeBom ? BOM : "") + TEST_STRING;
byte[] bytes = output.getBytes(encoding);
System.out.write(bytes);
FileOutputStream out = new FileOutputStream("uc-test-"
+ encoding + (writeBom ? "-bom.txt" : "-nobom.txt"));
out.write(bytes);
out.close();
}
}
}
}
默认代码页中的输出?完全是垃圾!
Z:\andrew\projects\sx\1259084>chcp
Active code page: 850
Z:\andrew\projects\sx\1259084>java Foo
== UTF-8
= no bom
ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
= bom
´╗┐ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
== UTF-16LE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
= bom
■A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
== UTF-16BE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
= bom
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
== UTF-32LE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺ ↓☺ z☺ |☺ D☺ B☺
R u s s i a n 0♦ 1♦ 2♦ 3♦ 4♦ 5♦ 6♦ M♦ N
♦ O♦
C J K `O }Y
= bom
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺ ↓☺ z☺ |☺ D☺ B☺
R u s s i a n 0♦ 1♦ 2♦ 3♦ 4♦ 5♦ 6♦ M♦ N
♦ O♦
C J K `O }Y
== UTF-32BE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
= bom
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
但是,如果我们对保存的文件执行type操作会怎样呢?它们包含的字节与打印到控制台的字节完全相同。
Z:\andrew\projects\sx\1259084>type *.txt
uc-test-UTF-16BE-bom.txt
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
uc-test-UTF-16BE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
uc-test-UTF-16LE-bom.txt
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
uc-test-UTF-16LE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
uc-test-UTF-32BE-bom.txt
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
uc-test-UTF-32BE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
uc-test-UTF-32LE-bom.txt
A S C I I a b c d e x y z
G e r m a n ä ö ü Ä Ö Ü ß
P o l i s h ą ę ź ż ń ł
R u s s i a n а б в г д е ж э ю я
C J K 你 好
uc-test-UTF-32LE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺ ↓☺ z☺ |☺ D☺ B☺
R u s s i a n 0♦ 1♦ 2♦ 3♦ 4♦ 5♦ 6♦ M♦ N
♦ O♦
C J K `O }Y
uc-test-UTF-8-bom.txt
´╗┐ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
uc-test-UTF-8-nobom.txt
ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
唯一 * 有效的是UTF-16LE文件,带有BOM,通过type打印到控制台。
如果我们使用type以外的任何东西来打印文件,我们会得到垃圾:
Z:\andrew\projects\sx\1259084>copy uc-test-UTF-16LE-bom.txt CON
■A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
1 file(s) copied.
Z:\andrew\projects\sx\1259084>chcp 65001
Active code page: 65001
Z:\andrew\projects\sx\1259084>java Foo
== UTF-8
= no bom
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
ж эюя
CJK 你好
你好
好
�
= bom
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
еж эюя
CJK 你好
你好
好
�
== UTF-16LE
= no bom
A S C I I a b c d e x y z
…
但是,设置Unicode UTF-8代码页的C程序:
#include <stdio.h>
#include <windows.h>
int main() {
int c, n;
UINT oldCodePage;
char buf[1024];
oldCodePage = GetConsoleOutputCP();
if (!SetConsoleOutputCP(65001)) {
printf("error\n");
}
freopen("uc-test-UTF-8-nobom.txt", "rb", stdin);
n = fread(buf, sizeof(buf[0]), sizeof(buf), stdin);
fwrite(buf, sizeof(buf[0]), n, stdout);
SetConsoleOutputCP(oldCodePage);
return 0;
}
7条答案
按热度按时间evrscar21#
是的,这是令人沮丧的有时候
type
和其他程序会打印乱码,有时候则不会。首先,Unicode字符将只显示if the current console font contains the characters。所以使用像Lucida Console这样的TrueType字体,而不是默认的光栅字体。
但是如果控制台字体不包含你想要显示的字符,你会看到问号而不是乱码,当你看到乱码时,不仅仅是字体设置。
当程序使用标准C-库I/O函数如
printf
时,程序的输出编码必须与控制台的输出编码匹配,否则会出现乱码。chcp
显示并设置当前代码页。所有使用标准C-库I/O函数的输出都被视为chcp
显示的代码页。将程序的输出编码与控制台的输出编码进行匹配可以通过两种不同的方式完成:
chcp
或GetConsoleOutputCP
获取控制台的当前代码页,并将自身配置为以该编码输出,或者chcp
或SetConsoleOutputCP
设置控制台的当前代码页,以匹配程序的默认输出编码。但是,使用Win32 API的程序可以使用
WriteConsoleW
将UTF-16LE字符串直接写入控制台。这是在不设置代码页的情况下获得正确输出的唯一方法。即使在使用该函数时,如果字符串不是以UTF-16LE编码开始的,Win32程序也必须将正确的代码页传递给MultiByteToWideChar
。此外,如果程序的输出被重定向,WriteConsoleW
将不起作用;在那种情况下需要更多的干预。type
有时会工作,因为它会检查每个文件的开头是否有UTF-16LE Byte Order Mark (BOM),即字节0xFF 0xFE
。如果它找到这样的标记,它会使用WriteConsoleW
显示文件中的Unicode字符,而不管当前代码页是什么。但是当type
处理任何没有UTF-16LE BOM的文件时,或者在任何不调用WriteConsoleW
的命令中使用非ASCII字符-您需要设置控制台代码页和程序输出编码以使其相互匹配。我们怎么才能知道呢?
下面是一个包含Unicode字符的测试文件:
下面是一个Java程序,它可以用一堆不同的Unicode编码打印出测试文件。它只将ASCII字符或编码字节打印到
stdout
。默认代码页中的输出?完全是垃圾!
但是,如果我们对保存的文件执行
type
操作会怎样呢?它们包含的字节与打印到控制台的字节完全相同。type
打印到控制台。如果我们使用
type
以外的任何东西来打印文件,我们会得到垃圾:从
copy CON
不能正确显示Unicode这一事实来看,我们可以得出结论,type
命令具有在文件开头检测UTF-16LE BOM并使用特殊Windows API打印它的逻辑。我们可以通过在调试器中打开
cmd.exe
来看到这一点,当它转到type
输出一个文件时:在
type
打开一个文件后,它检查0xFEFF
的BOM--即小端字节0xFF 0xFE
--如果有这样的BOM,type
设置一个内部fOutputUnicode
标志,这个标志稍后被检查以决定是否调用WriteConsoleW
。但这是让
type
输出Unicode的唯一方法,而且只适用于有BOM和UTF-16LE格式的文件。对于所有其他文件,以及没有特殊代码来处理控制台输出的程序,您的文件将根据当前代码页进行解释,并可能显示为乱码。您可以在自己的程序中模拟
type
如何将Unicode输出到控制台,如下所示:此程序用于使用默认代码页在Windows控制台上打印Unicode。
对于示例Java程序,我们可以通过手动设置代码页来获得一些正确的输出,尽管输出会以奇怪的方式混乱:
但是,设置Unicode UTF-8代码页的C程序:
输出正确:
故事的寓意是什么?
type
可以打印包含BOM的UTF-16LE文件,而不管当前代码页如何WriteConsoleW
将Unicode输出到控制台。chcp
,并可能仍然会得到奇怪的输出。owfi6suc2#
类型
查看当前代码页(正如Dewfy已经说过的)。
用途
以查看所有安装的代码页并找出代码页编号的含义。
要使用
nlsinfo
,您需要安装Windows Server 2003资源工具包(适用于Windows XP)。lsmd5eda3#
为了回答您的第二个问题:编码是如何工作的,Joel Spolsky编写了一个很棒的introductory article on this,强烈推荐。
col17t5w4#
Windows代码页的问题,以及C程序的可移植性和本地化问题让我很沮丧。之前的帖子已经详细地描述了这些问题,所以我不打算在这方面补充任何东西。
长话短说,最终我在Visual C++标准C库上编写了自己的UTF-8兼容库层,基本上,这个库确保标准C程序在任何代码页中都能正确工作,并在内部使用UTF-8。
该库称为MsvcLibX,可在https://github.com/JFLarvoire/SysToolsLib上作为开源提供。
MsvcLibX README on GitHub中的更多细节,包括如何构建库并在自己的程序中使用它。
上面GitHub库中的release section提供了几个使用这个MsvcLibX库的程序,这些程序将显示它的功能。尝试使用我的which.exe工具,在PATH中搜索具有非ASCII名称的目录,搜索具有非ASCII名称的程序,并更改代码页。
另一个有用的工具是conv.exe程序。此程序可以轻松地将数据流从任何代码页转换为任何其他代码页。其默认值是在Windows代码页中输入,并在当前控制台代码页中输出。这允许正确查看Windows GUI应用程序生成的数据(例如:记事本),只需使用一个简单的命令,如:
type WINFILE.txt | conv
这个MsvcLibX库并不完整,欢迎大家对它进行改进!
webghufk5#
命令CHCP显示当前代码页。它有三个数字:8xx和Windows 12xx是不同的。所以键入一个纯英文文本你不会看到任何区别,但一个扩展代码页(如西里尔)将打印错误。
zd287kbt6#
在Java中,我使用编码“IBM850”来编写文件,这样就解决了这个问题。
nfg76nw07#
只需创建文件%HOMEPATH%\init.cmd,即可控制代码页。
我的说: