"Исправить" строковое кодирование в Java
У меня есть String
создан из byte[]
массив, используя кодировку UTF-8.
Однако он должен был быть создан с использованием другой кодировки (Windows-1252).
Есть ли способ преобразовать эту строку обратно в правильную кодировку?
Я знаю, что это легко сделать, если у вас есть доступ к исходному байтовому массиву, но в моем случае уже слишком поздно, потому что он предоставляется библиотекой с закрытым исходным кодом.
4 ответа
Поскольку, кажется, существует некоторая путаница относительно того, возможно ли это или нет, я думаю, что мне нужно привести обширный пример.
Вопрос утверждает, что (начальный) вход является byte[]
который содержит данные в кодировке Windows-1252. Я позвоню byte[]
ib
(для "начальных байтов").
Для этого примера я выберу немецкое слово "Bär" (что означает "медведь") в качестве ввода:
byte[] ib = new byte[] { (byte) 0x42, (byte) 0xE4, (byte) 0x72 };
String correctString = new String(ib, "Windows-1252");
assert correctString.charAt(1) == '\u00E4'; //verify that the character was correctly decoded.
(Если ваша JVM не поддерживает эту кодировку, вы можете вместо этого использовать ISO-8859-1, потому что эти три буквы (и большинство других) находятся в одной и той же позиции в этих двух кодировках).
Далее вопрос утверждает, что какой-то другой код (который находится вне нашего влияния) уже преобразован, что byte[]
в строку с использованием кодировки UTF-8 (я назову это String
is
Для входной строки"). Тот String
это единственный вход, который доступен для достижения нашей цели (если is
были доступны, это было бы тривиально)
String is = new String(ib, "UTF-8");
System.out.println(is);
Это, очевидно, приводит к неправильному выводу "B ".
Целью было бы произвести ib
(или правильное декодирование этого byte[]
) только is
имеется в наличии.
Теперь некоторые люди утверждают, что получение байтов в кодировке UTF-8 из этого is
вернет массив с теми же значениями, что и исходный массив:
byte[] utf8Again = is.getBytes("UTF-8");
Но это возвращает кодировку UTF-8 двух символов B
а также �
и определенно возвращает неправильный результат при повторной интерпретации как Windows-1252:
System.out.println(new String(utf8Again, "Windows-1252");
В этой строке выводится "B�", что совершенно неверно (это также тот же вывод, который был бы результатом, если бы в исходном массиве содержалось не слово "Bür").
Таким образом, в этом случае вы не можете отменить операцию, потому что информация потеряна.
Фактически есть случаи, когда такие неправильные кодировки могут быть отменены. Это более вероятно, если все возможные (или, по крайней мере, встречающиеся) последовательности байтов действительны в этой кодировке. Так как UTF-8 имеет несколько последовательностей байтов, которые просто не являются допустимыми значениями, у вас будут проблемы.
Я попробовал это, и это почему-то сработало
Код для исправления проблемы с кодировкой (она не работает идеально, что мы скоро увидим):
final Charset fromCharset = Charset.forName("windows-1252");
final Charset toCharset = Charset.forName("UTF-8");
String fixed = new String(input.getBytes(fromCharset), toCharset);
System.out.println(input);
System.out.println(fixed);
Результаты:
input: …Und ich beweg mich (aber heut nur langsam)
fixed: …Und ich beweg mich (aber heut nur langsam)
Вот еще один пример:
input: Waun da wuan ned wa (feat. Wolfgang Kühn)
fixed: Waun da wuan ned wa (feat. Wolfgang Kühn)
Вот что происходит и почему вышеприведенный трюк работает:
- Исходный файл представлял собой текстовый файл в кодировке UTF-8 (через запятую)
- Этот файл был импортирован в Excel, НО пользователь по ошибке ввел Windows 1252 для кодировки (которая, вероятно, была кодировкой по умолчанию на его или ее компьютере)
- Пользователь думал, что импорт был успешным, потому что все символы в диапазоне ASCII выглядели хорошо.
Теперь, когда мы пытаемся "перевернуть" процесс, вот что происходит:
// we start with this garbage, two characters we don't want!
String input = "ü";
final Charset cp1252 = Charset.forName("windows-1252");
final Charset utf8 = Charset.forName("UTF-8");
// lets convert it to bytes in windows-1252:
// this gives you 2 bytes: c3 bc
// "Ã" ==> c3
// "¼" ==> bc
bytes[] windows1252Bytes = input.getBytes(cp1252);
// but in utf-8, c3 bc is "ü"
String fixed = new String(windows1252Bytes, utf8);
System.out.println(input);
System.out.println(fixed);
Код исправления кодировки, приведенный выше, работает, но не работает для следующих символов:
(Предполагается, что используются только 1-байтовые символы из Windows 1252):
char utf-8 bytes | string decoded as cp1252 --> as cp1252 bytes
” e2 80 9d | â€� e2 80 3f
Á c3 81 | Ã� c3 3f
Í c3 8d | Ã� c3 3f
Ï c3 8f | Ã� c3 3f
Рc3 90 | � c3 3f
Ý c3 9d | Ã� c3 3f
Это работает для некоторых персонажей, например:
Þ c3 9e | Þ c3 9e Þ
ß c3 9f | ß c3 9f ß
à c3 a0 | Ã c3 a0 à
á c3 a1 | á c3 a1 á
â c3 a2 | â c3 a2 â
ã c3 a3 | ã c3 a3 ã
ä c3 a4 | ä c3 a4 ä
å c3 a5 | Ã¥ c3 a5 å
æ c3 a6 | æ c3 a6 æ
ç c3 a7 | ç c3 a7 ç
ПРИМЕЧАНИЕ. - Первоначально я думал, что это относится к вашему вопросу (и, поскольку я сам работал над тем же, я решил поделиться с тем, что я узнал), но, похоже, моя проблема была немного другой. Может быть, это поможет кому-то еще.
То, что вы хотите сделать, невозможно. Если у вас есть Java String, информация о байтовом массиве теряется. Вам может повезти, делая "ручное преобразование". Создайте список всех символов windows-1252 и их отображение в UTF-8. Затем переберите все символы в строке, чтобы преобразовать их в правильную кодировку.
Редактировать: как сказал комментатор, это не будет работать. Когда вы преобразуете байтовый массив Windows-1252, как если бы это был UTF-8, вы обязательно получите исключения кодирования. (Смотрите здесь и здесь).