4

我有以下一段代码

public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(Charset.defaultCharset().toString());

        String accentedE = "é";

        String utf8 = new String(accentedE.getBytes("utf-8"), Charset.forName("UTF-8"));
        System.out.println(utf8);
        utf8 = new String(accentedE.getBytes(), Charset.forName("UTF-8"));
        System.out.println(utf8);
        utf8 = new String(accentedE.getBytes("utf-8"));
        System.out.println(utf8);
        utf8 = new String(accentedE.getBytes());
        System.out.println(utf8);
}

上面的输出如下

windows-1252
é
?
é
é

有人可以帮我理解这是做什么的吗?为什么这个输出?

4

3 回答 3

6

如果您已经有了String,则无需立即对其进行编码和解码,该字符串已经是某人已解码原始字节的结果。

在字符串文字的情况下,某人是编译器,将您的源作为原始字节读取并以您指定的编码对其进行解码。如果您已经以 Windows-1252 编码物理保存了源文件,并且编译器将其解码为 Windows-1252,那么一切都很好。如果没有,您需要通过声明编译器在编译源代码时使用的正确编码来解决此问题...

线

String utf8 = new String(accentedE.getBytes("utf-8"), Charset.forName("UTF-8"));

绝对什么都不做。(编码为 UTF-8,解码为 UTF-8 == 无操作)

线

utf8 = new String(accentedE.getBytes(), Charset.forName("UTF-8"));

将字符串编码为 Windows-1252,然后将其解码为 UTF-8。结果只能在 Windows-1252 中解码(因为它是在 Windows-1252 中编码的,呵呵),否则会得到奇怪的结果。

线

utf8 = new String(accentedE.getBytes("utf-8"));

将字符串编码为 UTF-8,然后将其解码为 Windows-1252。与前面的情况相同的原则适用。

线

utf8 = new String(accentedE.getBytes());

绝对什么都不做。(编码为 Windows-1252,解码为 Windows-1252 == 无操作)

用整数类比可能更容易理解:

int a = 555;
//The case of encoding as X and decoding right back as X
a = Integer.parseInt(String.valueOf(a), 10);
//a is still 555

int b = 555;
//The case of encoding as X and decoding right back as Y
b = Integer.parseInt(String.valueOf(b), 15);
//b is now 1205 I.E. strange result

这两者都是无用的,因为在执行任何代码之前我们已经拥有了我们需要的东西,整数555

当您的字符串离开您的系统时需要将其编码为原始字节,当原始字节进入您的系统时需要将其解码为字符串。无需在系统内直接进行编码和解码。

于 2013-03-19T13:24:31.513 回答
1

第 1 行 - 您系统上的默认字符集是 windows-1252。

第 2 行 - 您通过将字符串文字编码为 UTF-8 字节创建了一个字符串,然后使用 UTF-8 方案对其进行解码。结果是格式正确的String,可以使用windows-1252编码正确输出。

第 3 行 - 您通过将字符串文字编码为 windows-1252 创建了一个字符串,然后使用 UTF-8 对其进行解码。UTF-8 解码器检测到一个不可能是 UTF-8 的序列,并用问号“?”替换了有问题的字符。(UTF-8 格式表示,任何最高位设置为 1 的字节都是多字节字符的一个字节。但是 windows-1252 编码只有一个字节长......所以,这是糟糕的 UTF- 8)

第 4 行 - 您通过 UTF-8 编码然后在 windows-1252 中解码创建了一个字符串。在这种情况下,解码并没有“失败”,但它产生了垃圾(又名 mojibake)。输出 2 个字符的原因是“é”的 UTF-8 编码是 2 字节序列。

第 5 行 - 您通过编码为 windows-1252 并解码为 windows-1252 创建了一个字符串。这会产生正确的输出。


总的教训是,如果您使用一种字符编码将字符编码为字节,然后使用不同的字符编码进行解码,您很可能会受到一种或另一种形式的破坏。

于 2013-03-19T13:30:29.327 回答
0

当您调用 String getBytes方法时:

使用平台的默认字符集将此字符串编码为字节序列,并将结果存储到新的字节数组中。

所以每当你这样做:

accentedE.getBytes()

它将重音字符串的内容作为在默认操作系统代码页中编码的字节,在您的情况下为 cp-1252

这一行:

new String(accentedE.getBytes(), Charset.forName("UTF-8"))

获取重音字节(以 cp1252 编码)并尝试以 UTF-8 解码它们,因此出现错误。另一方的情况相同:

new String(accentedE.getBytes("utf-8"))

getBytes 方法采用以 cp-1252 编码的重音字节,以 UTF-8 重新编码它们,但随后 String构造函数使用默认的 OS 代码页(即 cp-1252)对它们进行编码。

通过使用平台的默认字符集解码指定的字节数组来构造一个新的字符串。新字符串的长度是字符集的函数,因此可能不等于字节数组的长度。

我强烈推荐阅读这篇优秀的文章:

每个软件开发人员绝对、绝对必须了解 Unicode 和字符集的绝对最低要求(没有借口!)

更新:

简而言之,每个字符都存储为一个数字。为了知道哪个字符是哪个数字,操作系统使用代码页。考虑以下代码段:

String accentedE = "é";

System.out.println(String.format("%02X ", accentedE.getBytes("UTF-8")[0]));
System.out.println(String.format("%02X ", accentedE.getBytes("UTF-8")[1]));
System.out.println(String.format("%02X ", accentedE.getBytes("windows-1252")[0]));

输出:

C3 
A9 
E9

这是因为UTF-8中的小重音 e 存储为两个字节的值C3A9,而在cp-1252中存储为单个字节的值E9。有关详细说明,请阅读链接的文章。

于 2013-03-19T13:24:56.760 回答