JDK 7和8中使用byte数组创建新字符串的结果不同

5

使用新的String(byte[], "UTF-8")创建的一些字节数组在jdk 1.7和1.8中返回不同的结果。

byte[] bytes1 = {55, 93, 97, -13, 4, 8, 29, 26, -68, -4, -26, -94, -37, 32, -41, 88};
        String str1 = new String(bytes1,"UTF-8");
        System.out.println(str1.length());

        byte[] out1 = str1.getBytes("UTF-8");
        System.out.println(out1.length);
        System.out.println(Arrays.toString(out1));

byte[] bytes2 = {65, -103, -103, 73, 32, 68, 49, 73, -1, -30, -1, -103, -92, 11, -32, -30};
        String str2 = new String(bytes2,"UTF-8");
        System.out.println(str2.length());

        byte[] out2 = str2.getBytes("UTF-8");
        System.out.println(out2.length);
        System.out.println(Arrays.toString(out2));

在使用new String(byte[],"UTF-8")方式将字节数组转为字符串时,jdk7和jdk8中结果(str2)不相同,但byte1却一样。bytes2有什么特殊之处?

测试"ISO-8859-1"编码,结果发现在jdk1.8中bytes2的结果是相同的!

jdk1.7.0_80:

15
27
[55, 93, 97, -17, -65, -67, 4, 8, 29, 26, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, 32, -17, -65, -67, 88]
15
31
[65, -17, -65, -67, -17, -65, -67, 73, 32, 68, 49, 73, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, 11, -17, -65, -67]

jdk1.8.0_201

15
27
[55, 93, 97, -17, -65, -67, 4, 8, 29, 26, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, 32, -17, -65, -67, 88]
16
34
[65, -17, -65, -67, -17, -65, -67, 73, 32, 68, 49, 73, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, 11, -17, -65, -67, -17, -65, -67]

4
可能相关:https://dev59.com/XIvda4cB1Zd3GeqPZ32r 更好的解决方案:https://dev59.com/LF8e5IYBdhLWcg3wwMlW - assylias
在Java 10中执行此操作时,您将获得与Java 8相同的结果。 - dan1st
1个回答

9

简短回答:

在第二个字节数组中,最后2个字节:[-32, -37] (0b11011011_11100000) 被编码为:

By JDK 7: [-17, -65, -67] which is Unicode character 0xFFFD ("invalid character"),
By JDK 8: [-17, -65, -67, -17, -65, -67] which is 2 of 0xFFFD characters.

长答案:

你的数组中的某些字节序列似乎不是有效的UTF-8序列。 让我们考虑这段代码:

byte[] bb = {55, 93, 97, -13, 4, 8, 29, 26, -68, -4, -26, -94, -37, 32, -41, 88};
for (byte b : bb) System.out.println(Integer.toBinaryString(b & 0xff));

它将打印出(我手动添加前导下划线以提高可读性):
__110111
_1011101
_1100001
11110011
_____100
____1000
___11101
___11010
10111100
11111100
11100110
10100010
11011011
__100000
11010111
_1011000

正如您可以在UTF-8维基百科文章中阅读到的那样,UTF-8编码的字符串使用以下二进制序列:

0xxxxxxx -- for ASCII characters
110xxxxx 10xxxxxx -- for 0x0080 to 0x07ff
1110xxxx 10xxxxxx 10xxxxxx -- for 0x0800 to 0xFFFF
... and so on

因此,不遵循此编码方案的每个字符都会被替换为3个字节:

[-17,-65,-67]
二进制为11101111 10111111 10111101
Unicode位为0b11111111_11111101
Unicode十六进制为0xFFFD(Unicode的“无效字符”)

您的代码打印的数组唯一的区别在于如何处理以下字符,这些字符是第二个数组末尾的2个字节:

[-32, -30] is 0b11100000_11100010, and this is not valid UTF-8

JDK 7针对此序列生成了单个0xFFFD字符。
JDK 8针对此序列生成了两个0xFFFD字符。
RFC-3629标准对如何处理无效序列没有明确的说明,因此在JDK 8中,他们决定为每个无效字节生成0xFFFD,这似乎更正确。
另一个问题是,为什么要尝试将这样的原始非UTF-8字节解析为UTF-8字符,当你不应该这样做?

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