为什么使用new String(bytes, enc).getBytes(enc)方法不能返回原始的字节数组?

17

我进行了以下的 "模拟":

byte[] b = new byte[256];

for (int i = 0; i < 256; i ++) {
    b[i] = (byte) (i - 128);
}
byte[] transformed = new String(b, "cp1251").getBytes("cp1251");

for (int i = 0; i < b.length; i ++) {
    if (b[i] != transformed[i]) {
        System.out.println("Wrong : " + i);
    }
}
对于cp1251,这只输出一个错误的字节——在第25个位置。对于KOI8-R,完全正常。对于cp1252,有4或5个差异。
这是什么原因,如何克服呢?
我知道将字节数组表示为任何编码的字符串都是错误的,但这是支付提供程序协议的要求,所以我别无选择。
更新:用ISO-8859-1表示它可以工作,我将使用它来表示byte[]部分,而使用cp1251表示文本部分,因此问题仅出于好奇。
5个回答

12

目标集中不支持一些“字节”-它们会被替换成?字符。当您转换回去时,?通常会转换为字节值63-这不是它之前的值。


太棒了。我实际上是在寻找.NET的答案,但它们的行为非常相似,所以我从这里得到了启示。谢谢。 - John K

7

这是什么原因

原因在于字符编码不一定是双射的,而且没有理由指望它们是。并非所有字节或字节序列都在所有编码中合法,通常非法序列会被解码为某种占位符字符,如'?'或U+FFFD,当重新编码时,这些字符显然不能产生相同的字节。

此外,一些编码可能将一些合法的不同字节序列映射到相同的字符串。


4
看起来,cp1251和cp1252都有一些字节值不对应定义的字符,即它们是“无法映射”的。 String(byte[], String)的javadoc说:
当给定的字节在给定的字符集中无效时,此构造函数的行为未指定。 如果需要更多对解码过程的控制,则应使用CharsetDecoder类。
其他构造函数说:
此方法始终将格式错误的输入和无法映射的字符序列替换为此字符集的默认替换字符串。
如果在实践中看到这种情况发生,这表明您使用了错误的字符集或者您收到了一些错误的数据。无论哪种情况,以无问题的方式继续可能并不是一个好主意。
我一直在尝试找出是否有办法让CharsetDecoder“保留”无法映射的字符,但我认为除非您愿意实现自定义的解码器/编码器对,否则不可能做到。 但我也得出结论,即即使尝试保留这些无法映射的字符也是没有意义的。理论上,将这些无法映射的字符映射为真正的Unicode代码点是错误的。如果这样做,您的应用程序又该如何处理这些字符呢?

3

实际上有一个区别:值为24的字节被转换为值为0xFFFDchar,这是“Unicode替换字符”,用于无法翻译的字节。当转换回来时,你会得到一个问号(值为63)。

在CP1251中,代码24意味着“输入结束”,不能成为正确字符串的一部分,这就是为什么Java认为它是“无法翻译”的原因。


2

历史原因:在古老的字符编码(EBCDIC,ASCII)中,前32个代码具有特殊的“控制”含义,可能无法映射到可读取的字符。例如:退格,响铃,回车。较新的字符编码标准通常会继承此功能,并且不为前32个位置中的每一个定义Unicode字符。Java字符是Unicode。


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