我有以下代码
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
é
?
é
é
有人可以帮我理解这是做什么的吗?为什么输出这个?
答案 0 :(得分:6)
如果您已经有String
,则无需对其进行编码和解码,该字符串已经是某人解码了原始字节的结果。
在字符串文字的情况下,某人是编译器将源读取为原始字节并使用您指定的编码对其进行解码。如果你已经用Windows-1252编码物理保存了源文件,并且编译器将其解码为Windows-1252,那么一切都很好。如果没有,您需要通过声明编译器在编译源代码时使用的正确编码来解决此问题...
该行
String utf8 = new String(accentedE.getBytes("utf-8"), Charset.forName("UTF-8"));
绝对没有。 (编码为UTF-8,解码为UTF-8 == no-op)
该行
utf8 = new String(accentedE.getBytes(), Charset.forName("UTF-8"));
将字符串编码为Windows-1252,然后将其解码为UTF-8。结果必须只能在Windows-1252中解码(因为它在Windows-1252中编码,duh),否则你会得到奇怪的结果。
该行
utf8 = new String(accentedE.getBytes("utf-8"));
将字符串编码为UTF-8,然后将其解码为Windows-1252。同样的原则适用于以前的情况。
该行
utf8 = new String(accentedE.getBytes());
绝对没有。 (编码为Windows-1252,解码为Windows-1252 == no-op)
比较可能更容易理解的整数:
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
。
需要 当离开系统时将字符串编码为原始字节,并且当进入系统时需要将原始字节解码为字符串。无需在系统中右后方进行编码和解码。
答案 1 :(得分:1)
第1行 - 系统上的默认字符集是windows-1252。
第2行 - 您通过将字符串文字编码为UTF-8字节来创建字符串,然后使用UTF-8方案对其进行解码。结果是正确形成的String,可以使用windows-1252编码正确输出。
第3行 - 您通过将字符串文字编码为windows-1252创建了一个String,然后使用UTF-8对其进行解码。 UTF-8解码器检测到一个不可能是UTF-8的序列,并用问号“?”替换了有问题的字符。 (UTF-8格式表示将顶部位设置为1的任何字节都是多字节字符的一个字节。但是windows-1252编码只有一个字节长...... ergo,这是坏的UTF- 8)
第4行 - 您通过UTF-8编码创建了一个String,然后在windows-1252中解码。在这种情况下,解码没有“失败”,但它产生了垃圾(aka mojibake)。您输出2个字符的原因是“é”的UTF-8编码是2字节序列。
第5行 - 您通过编码为windows-1252创建了一个String,并解码为windows-1252。这会产生正确的输出。
总的教训是,如果使用一个字符编码将字符编码为字节,然后使用不同的字符编码进行解码,则可能会出现一种或另一种形式的错误。
答案 2 :(得分:0)
当你调用String getBytes方法时:
使用平台的默认字符集将此String编码为字节序列,并将结果存储到新的字节数组中。
所以每当你这样做:
accentedE.getBytes()
在默认操作系统代码页中,它将accentedE String的内容作为字节encoded,在您的情况下为cp-1252。
这一行:
new String(accentedE.getBytes(), Charset.forName("UTF-8"))
获取重音的E字节(在cp1252中编码)并尝试以UTF-8解码它们,因此出错。另一方面的情况相同:
new String(accentedE.getBytes("utf-8"))
getBytes方法接受cp-1252中编码的accentedE字节,以UTF-8重新编码,但字符串constructor使用默认的OS代码页cp-1252对它们进行编码。
使用平台的默认字符集解码指定的字节数组,构造一个新的String。新String的长度是字符集的函数,因此可能不等于字节数组的长度。
我强烈建议您阅读这篇优秀的文章:
更新:
简而言之,每个字符都存储为数字。为了知道哪个字符是操作系统使用代码页的编号。请考虑以下代码段:
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中的小重音e存储为值E9
的单个字节。有关详细说明,请阅读链接文章。