Подтвердить что ты не робот

Почему новый String (bytes, enc).getBytes(enc) не возвращает исходный массив байтов?

Я сделал следующее "симуляцию":

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 для текстовой части, поэтому вопрос остается только из любопытства

4b9b3361

Ответ 1

Некоторые из "байтов" не поддерживаются в целевом наборе - они заменяются символом ?. Когда вы конвертируете назад, ? обычно преобразуется в значение байта 63 - это не то, что было раньше.

Ответ 2

В чем причина этого

Причина в том, что кодировки символов необязательно bijective, и нет веских оснований ожидать их. Не все байты или байтовые последовательности являются законными во всех кодировках, и обычно незаконные последовательности декодируются на какой-то символ-заполнитель, например '?' или U + FFFD, который, конечно же, не создает одинаковые байты при повторном кодировании.

Кроме того, некоторые кодировки могут отображать некоторые законные разные последовательности байтов в одну и ту же строку.

Ответ 3

Похоже, что и cp1251 и cp1252 имеют байтовые значения, которые не соответствуют определенным символам; то есть они "не могут быть".

В javadoc для String(byte[], String) сказано следующее:

Поведение этого конструктора, когда данные байты недопустимы в данной кодировке, не указывается. Класс CharsetDecoder должен использоваться, когда требуется больше контроля процесса декодирования.

Другие конструкторы говорят это:

Этот метод всегда заменяет последовательности неправильного ввода и неуправляемого символа с этой строкой замены по умолчанию для charset.

Если вы видите, что подобное происходит на практике, это указывает на то, что либо вы используете неправильный набор символов, либо вам дали некоторые плохие данные. В любом случае, это, вероятно, не очень хорошая идея, как если бы не было проблем.

Я пытался выяснить, есть ли способ заставить CharsetDecoder "сохранять" неуправляемые символы, и я не думаю, что это возможно, если вы не захотите реализовать собственную пару декодера/кодировщика. Но я также пришел к выводу, что даже попробовать не имеет смысла. Это (теоретически) неверно сопоставить эти несменяемые символы с реальными кодовыми точками Юникода. И если вы это сделаете, как ваше приложение будет обращаться с ними?

Ответ 4

На самом деле должно быть одно различие: байт значения 24 преобразуется в значение char значения 0xFFFD; что "символ замены Unicode" используется для непереводимых байтов. При преобразовании назад вы получаете знак вопроса (значение 63).

В CP1251 код 24 означает "конец ввода" и не может быть частью правильной строки, поэтому Java считает это "непереводимым".

Ответ 5

Историческая причина: в древних кодировках символов (EBCDIC, ASCII) первые 32 кода имеют особое значение "управления", и они могут не отображаться на читаемые символы. Примеры: backspace, bell, возврат каретки. Новые стандарты кодирования символов обычно наследуют это, и они не определяют символы Unicode для каждой из первых 32 позиций. Java-символы - Unicode.