«Исправить» строковое кодирование в Java - PullRequest
20 голосов
/ 12 апреля 2010

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

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

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

Ответы [ 4 ]

24 голосов
/ 12 апреля 2010

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

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

10 голосов
/ 01 мая 2014

Я попробовал это, и почему-то это сработало

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

 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)

Вот что происходит и почему приведенный выше трюк, похоже, работает:

  1. Исходный файл представлял собой текстовый файл в кодировке UTF-8 (через запятую)
  2. Этот файл был импортирован с помощью Excel, НО пользователь по ошибке ввел Windows 1252 для кодировки (которая, вероятно, была кодировкой по умолчанию на его или ее компьютере)
  3. Пользователь считает, что импорт был успешным, потому что все символы в диапазоне 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);

Код исправления кодировки, приведенный выше, работает, но не работает для следующих символов:

(при условии, что в Windows 1252 использовались только 1-байтовые символы):

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           ç

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

0 голосов
/ 12 апреля 2010

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

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

0 голосов
/ 12 апреля 2010

Вы можете использовать этот учебник

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...