четверг, 13 января 2011 г.

Русская буква «И» при формировании строки из массива байт


Дано:

При получении массива байт извне (например, через jni-буфер) необходимо преобразовать его в строку Java, с русскими буквами.  

В чём сложность:
(отличное описание взято отсюда http://www.skipy.ru/technics/encodings.html):

Символ "И" кодируется в UTF-8 последовательностью из двух байтов: 0xD0,0x98. Соответственно, при чтении в кодировке windows-1251 каждый из этих байтов декодируется по схеме данной кодировке. Так вот, я обнаружил следующее – код 0x98 не определен в наборе символов windows-1251 и, соответственно, в схеме этой кодировки. Вот схема кодировки cp1251, взятая с сайта Microsoft, а вот ее описание (взято отсюда). Как вы можете видеть, код 0x98 описан как UNDEFINED.

Проблема и решение:

Допустим откуда-то извне к нам попал массив байт, с неизвестной кодировкой. И с этим массивом надо проделать некоторые преобразования, то возможен хитрый ньюанс. Если на входе кодировка UTF-8, а в массиве есть русская буква И, то при попытке в java-коде создать строку с кодировкой windows-1251, мы получим «?»(он же - неопределённый байт) в созданной строке. При дальнейшем преобразовании в байты, этот символ просто потеряется (зачем нужны такие преобразования, я тут рассматривать не буду, но иногда такие вещи случаются :-)).

Немного кода :

// получаем правильную последовательность байт (то, что пришло нам извне)
byte[] bytes = «И-диотизм».getBytes("UTF-8");

// Создаём строку с кодировкой по умолчанию
// Напечатает «Каракули=Р?-диотизм», где видно, что один символ «?»
System.out.println("Каракули=" + new String(bytes, "Cp1251"));
System.out.println("Каракули=" + new String(bytes));  // аналогично

// Дальнейшее преобразование и потеря информации
String s1251_x = new String(bytes);
 String xx = new String(s1251_x.getBytes(), "UTF-8"); // даже в изначально правильной кодировке
// Выведет «xx=??-диотизм,len=10»
 System.out.println("xx=" + xx + ",len=" + xx.getBytes().length);

Решение :

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

// Если сразу сделать так
String sUtf8 = new String(bytes, "UTF-8");
// то и дальнейшие преобразования не потеряют информацию на входе
// Напечатает «sUtf8=И-диотизм,len=17»
System.out.println("sUtf8=" + sUtf8 + ",len=" + bytes.length);
 String s1251_2 = new String(sUtf8.getBytes(), "Cp1251");
// Напечатает «s1251_2=И-диотизм»
 System.out.println("s1251_2=" + s1251_2);

P.S.: Я эти заметки пишу для себя, и для конкретной ситуации, которая случилась в нашем коде. О глобальном подходе к кодированию/декодированию очень понятно и хорошо написано тут : http://www.skipy.ru/technics/encodings.html

2 комментария:

  1. А при перекодировке обычного текста на UFT8 в Windows1251 тоже вопросики появляются в место буквы "И"? Не раз сталкивался с такой проблемой, при перекодировки базы mysql в Windows1251, но понять не мог, от куда вопросики появляются в некоторых словах.

    ОтветитьУдалить
  2. Alexey,
    думаю, что это как-раз та самая ситуация с кодом 0x98 - UNDEFINED. Но мне кажется, что в mysql должны быть какие-то средства, которые позволяют решить данную проблему, т.е. явно указать из какой кодировку и в какую надо перекодировать. К сожалению мы используем другую БД и конкретно ничего сказать про mysql не могу

    ОтветитьУдалить