"Исправить" Строковое кодирование в Java

У меня есть String, созданный из массива byte[], используя кодировку UTF-8.
Однако он должен был быть создан с использованием другой кодировки (Windows-1252).

Есть ли способ преобразовать эту строку в правильную кодировку?

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

Ответ 1

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

Вопрос утверждает, что (начальный) ввод является 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[] в String с использованием кодировки 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 имеет несколько байтовых последовательностей, которые являются просто недопустимыми значениями, у вас есть.

Ответ 2

Я пробовал это, и он по какой-то причине работал

Код для исправления проблемы с кодировкой (он не работает отлично, что мы увидим в ближайшее время):

 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           ç

ПРИМЕЧАНИЕ. Я изначально думал, что это имеет отношение к вашему вопросу (и, поскольку я работал над тем же самым, я решил, что поделюсь тем, что узнал), но, похоже, моя проблема была несколько иной. Возможно, это поможет кому-то другому.

Ответ 3

То, что вы хотите сделать, невозможно. Когда у вас есть Java String, информация о массиве байтов будет потеряна. Возможно, вам удалась сделать "ручную конверсию". Создайте список всех символов Windows-1252 и их отображение в UTF-8. Затем перебирайте все символы в строке, чтобы преобразовать их в правильную кодировку.

Edit: Как сказал комментатор, это не сработает. Когда вы конвертируете массив байтов Windows-1252, поскольку он был UTF-8, вы обязаны получать исключения для кодирования. (Здесь здесь и здесь).

Ответ 4

Вы можете использовать этот tutorial

Необходимая кодировка должна быть определена в rt.jar(согласно this)