Трудности перевода

Несколько раз пришлось столкнуться с проблемами при чтении/записи кириллицы на Java. К примеру, есть файл в кодировке UTF-8, который читается с помощью java.io.FileInputStream. В результате русские буквы портятся. Бывает, что портятся не все, а только «И» и «ш». Проблема проявляется только на Windows, классический случай — разработчик сидит на Linux: «а у меня всё работает!».

Пришло время привести в порядок базовые знания.

В языке Java для строк используется тип java.lang.String. Символы в строке имеют тип Character — это 16-битные коды символов в кодировке Unicode.

Кодировка файлов может обеспечивать представление всех Unicode символов (UTF-8, UTF-16) или его подмножества (windows-1251). UTF-8 обеспечивает более компактное хранение наиболее распространенных символов (ASCII), но требует более сложного преобразования, поэтому существуют различные представления Unicode.

Что происходит, когда файл читается с помощью java.io.FileInputStream? Поток байтов преобразуется в последовательность Unicode символов. Стоп. Чтобы правильно преобразовать, надо знать кодировку файла. Но ни один из конструкторов FileInputStream не позволяет указать кодировку! Небольшое исследование выводит на инфрмацию о том, что используется умолчальная (дефолтная, стандартная) кодировка платформы. Что будет, если на самом деле в файле использована другая кодировка? Правильно, мусор. Как это победить? Есть такое свойство file.encoding, которое позволяет поменять стандартную кодировку, но оно не документировано (помните книжки типа «Недокументированные возможности MS Word 6.0?»).

Ладно, прочитали из файла строку в неправильной кодировке. В строке мусор. А мы этого не заметили и записали эту строку в другой файл. Конечно, через java.io.FileOutputStream. Тоже не имея возможности указать кодировку. В этом случае — к счастью. Потому что обратная функция почти все символы из мусора вернет в нормальное состояние. Кроме, если я не ошибаюсь, «И» и «ш». А мы будем долго удивляться, откуда такая выборочная беда, ну ведь почти всё же правильно работает!

Лучше бы все символы поломались. Потому что понимание проблемы в этом случае пришло бы быстрее. Вот пример того, что получается при двойной неправильной перекодировке (потоки тут не используются, но суть та же):

import java.io.UnsupportedEncodingException;
public class StreamEncoding
{
    public static void main( String[] args ) throws UnsupportedEncodingException
    {
        String s = "Иллюзия";
        System.out.println( new String(  new String( s.getBytes( "UTF-8" ), "windows-1251" )
                                        .getBytes( "windows-1251" )
                                      , "UTF-8"
                                      )
                          );
    }
}

Результат вывода:
�?ллюзия

Редкий случай — удалось понять суть проблемы. Обычно удаётся устранить проявления, но не докопаться до сути. Как же делать правильно?

  1. В своих программах для файловых операций использовать java.io.InputStreamReader/Writer и их конструкторы с указанием кодировки. Не использовать более простые File(Reader/Writer), потому что они не позволяют указать кодировку. И уж тем более не использовать File(Input/Output)Stream.
  2. Чужие программы иногда удается победить указанием свойства file.encoding в командной строке Java-машины. Очевидно, не всегда.

А почему под Linux всё работало? Потому что файл был в кодировке UTF-8, а это стандартная кодировка платформы в Linux. Только не надо нос задирать, в следующий раз попадется windows-1251 и всё будет наоборот.

04.02.2010  Метки:   Рубрики: Разработка, Языки

Один комментарий

  1. Андрей - 13.05.2010

    Спасибо большое за статью… Очень помогла, а то боролся несколько часов 8)

Написать комментарий