Новые результаты String (byte []) отличаются в JDK 7 и 8 - PullRequest
5 голосов
/ 07 мая 2019

Некоторые байтовые массивы, использующие новую строку (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));

bytes2 использует новую строку (byte [], "UTF-8"), результат (str2) не одинаков в jdk7 и jdk8, но byte1 такой же. Что особенного в байтах2?

Проверьте код "ISO-8859-1", результат в байтах2 такой же, как в jdk1.8!

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]

1 Ответ

9 голосов
/ 07 мая 2019

Краткий ответ:

Во втором байтовом массиве последние 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]
В двоичном формате 1110 1111 10 111111 10 111101
Unicode-биты: 0b11111111_11111101
Гекс Юникода равен 0xFFFD («Неверный символ Юникода»)

Единственная разница в массивах, напечатанных вашим кодом, заключается в том, как обрабатываются следующие символы, это 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, когда вам не следует этого делать?

...