Ответ 1
Некоторые из "байтов" не поддерживаются в целевом наборе - они заменяются символом ?
. Когда вы конвертируете назад, ?
обычно преобразуется в значение байта 63 - это не то, что было раньше.
Я сделал следующее "симуляцию":
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
для текстовой части, поэтому вопрос остается только из любопытства
Некоторые из "байтов" не поддерживаются в целевом наборе - они заменяются символом ?
. Когда вы конвертируете назад, ?
обычно преобразуется в значение байта 63 - это не то, что было раньше.
В чем причина этого
Причина в том, что кодировки символов необязательно bijective, и нет веских оснований ожидать их. Не все байты или байтовые последовательности являются законными во всех кодировках, и обычно незаконные последовательности декодируются на какой-то символ-заполнитель, например '?' или U + FFFD, который, конечно же, не создает одинаковые байты при повторном кодировании.
Кроме того, некоторые кодировки могут отображать некоторые законные разные последовательности байтов в одну и ту же строку.
Похоже, что и cp1251 и cp1252 имеют байтовые значения, которые не соответствуют определенным символам; то есть они "не могут быть".
В javadoc для String(byte[], String)
сказано следующее:
Поведение этого конструктора, когда данные байты недопустимы в данной кодировке, не указывается. Класс
CharsetDecoder
должен использоваться, когда требуется больше контроля процесса декодирования.
Другие конструкторы говорят это:
Этот метод всегда заменяет последовательности неправильного ввода и неуправляемого символа с этой строкой замены по умолчанию для charset.
Если вы видите, что подобное происходит на практике, это указывает на то, что либо вы используете неправильный набор символов, либо вам дали некоторые плохие данные. В любом случае, это, вероятно, не очень хорошая идея, как если бы не было проблем.
Я пытался выяснить, есть ли способ заставить CharsetDecoder "сохранять" неуправляемые символы, и я не думаю, что это возможно, если вы не захотите реализовать собственную пару декодера/кодировщика. Но я также пришел к выводу, что даже попробовать не имеет смысла. Это (теоретически) неверно сопоставить эти несменяемые символы с реальными кодовыми точками Юникода. И если вы это сделаете, как ваше приложение будет обращаться с ними?
На самом деле должно быть одно различие: байт значения 24 преобразуется в значение char
значения 0xFFFD
; что "символ замены Unicode" используется для непереводимых байтов. При преобразовании назад вы получаете знак вопроса (значение 63).
В CP1251 код 24 означает "конец ввода" и не может быть частью правильной строки, поэтому Java считает это "непереводимым".
Историческая причина: в древних кодировках символов (EBCDIC, ASCII) первые 32 кода имеют особое значение "управления", и они могут не отображаться на читаемые символы. Примеры: backspace, bell, возврат каретки. Новые стандарты кодирования символов обычно наследуют это, и они не определяют символы Unicode для каждой из первых 32 позиций. Java-символы - Unicode.