WebClub - Всероссийский Клуб Веб-разработчиков
WebClub.RU » Архив » Java: Русские буквы и не только...

Java: Русские буквы и не только...


Дата публикации: 17-03-2013

Файлы данных, потоки, БД.

 

Итак, как все, надеюсь, знают, в языке Java для представления символов используется Unicode, т.е. по два байта на один символ (тип char размером в 16 бит). В набор символов входят всевозможные буквы со всякими ч±рточками и припендюльками, греческие, математические и символы псевдографики. В том числе и так любимые нами символы кириллицы (диапазон значений 0x0400-0x04ff). Так что с этой стороны никакой дискриминации нет.

 

С другой стороны большинство файлов данных основано на 8-битовом представлении символов. Сюда входят также текстовые файлы и большинство баз данных (окромя наиболее продвинутых). Кроме того, что самое паршивое, одни и те же байты могут представлять разные символы (в зависимости от кодовой страницы). Налицо конфликт - как преобразовать одно в другое и наоборот, прич±м с наименьшими потерями для данных. Для этого был придуман довольно удобный механизм использования кодовых страниц. Для каждой кодовой страницы было созданно по 2 класса перекодировки (ByteToChar и CharToByte). Классы эти лежат с пакете sun.io. Если, при перекодировке из char в byte не было найдено соответсвующего символа, он заменяется на символ ?.

 

Кстати, эти файлы кодовых страниц в некоторых ранних версиях JDK 1.1 содержат ошибки, вызывающие ошибки перекодировок, а то и вообще исключения при выполнении. Например, это касается кодировки KOI8_R. Лучшее, что можно при этом сделать - сменить версию на более позднюю. Судя по Sun-овскому описанию, большинство этих проблем было решено в версии JDK 1.1.6.

 

Когда и как надлежит пользоваться этой перекодировкой? Когда пользоваться, в принципе, понятно - при любом преобразовании из byte в char и наоборот. В классе String в тех местах, где есть преобразование можно указать дополнительный параметр (String enc), задающий имя кодовой страницы. Это конструктор по массиву байтов и метод getBytes(). Однако, в реальной программе, явно указывать кодовую страницу не всегда удобно. Для этого была введена кодировка по умолчанию. По умолчанию она зависит от системы (для русских виндов принята кодировка Cp1251), и е± можно изменить установкой системного свойства file.encoding. Эта кодировка используются тогда, когда явно не указанно название страницы. Т.к. эта настройка одна на все преобразования, иногда можно наткнуться на неприятности. Например, эта же настройка используется для вывода на консольный экран, что, в случае виндов, как правило неприемлемо - там нужно использовать страницу Cp866. Было бы здорово, если бы эти кодировки указывались независимо - например console.encoding и т.п., но, думаю, Sun-овцам пока не до таких высоких материй.

 

Кстати, о выводе на консоль. Есть два пути решения вышеуказанной проблемы:

 

Использовать вместо System.out.println свой класс, а уже в н±м делать преобразование. Например:

 public class Msg

 {

  static String cp = System.getProperty("console.encoding","Cp866");

 

  public static void message(String msg)

  {

   msg += "\n";

   byte[] b;

 

   try { b = msg.getBytes(cp); }

   catch( UnsupportedEncodingException e )

     {

      b = msg.getBytes();// В случае отсутствия нужной кодировки,

// делаем преобразование по умолчанию

     }

 

   System.out.write(b);

  }

 }

 

 ...

 Msg.message("Сообщение");

Написать свою версию PrintStream, поддерживающую нужную кодировку, и подставить его через System.setOut() и System.setErr(). Вот, например, обычное начало в моих программах:

 ...

 public static void main(String[] args)

 {

  // Установка вывода консольных сообщений в нужной кодировке

  try

    {

     System.setOut(new CodepagePrintStream(System.out,System.getProperty("console.encoding","Cp866")) );

    }

  catch(UnsupportedEncodingException e)

    {

     System.out.println("Unable to setup console codepage: " + e);

    }

 ...

Куда же податься бедному российскому программисту? Не отчаивайтесь, есть множество путей прочитать и сохранить данные из файлов в нужной Вам кодировке.

 

Читать и записывать массивы байтов (byte[]), а для перекодировки использовать упомянутые методы класса String. Этот способ особенно удобен, когда в потоке могут присутствовать данные в разных кодировках.

Использовать классы InputStreamReader и OutputStreamWriter из пакета java.io, специально предназначенные для этих целей.

Сделать преобразование в нужную кодировку. Если вы вс± сделаете корректно, то данные не потеряются, хотя, конечно, пользоваться этим желательно только в крайнем случае. Пример:

 // Чтение русских букв в кодировке Cp866 через объект,

 // поддерживающий только Cp437

 String str = o.readString();

 

 str = new String(str.getBytes("Cp437"),"Cp866");

 

 // Сохранение русских букв в кодировке Cp866 через объект,

 // поддерживающий только Cp437

 str = new String(str.getBytes("Cp866"),"Cp437");

 

 o.writeString(str);

Настроить драйвер БД на нужную кодировку. Это нужно в том случае, если драйвер поддерживает Unicode или же используется сортировка данных средствами SQL. Как именно - это зависит от конкретного драйвера.

Например, один из самых часто используемых драйверов - мост JDBC-ODBC. В версиях JDK 1.1, этот мост просто игнорировал кодировки символов, из-за чего нужно было предпринять дополнительные ухищрения, типа описанных в предыдущем пункте (это также касается и последней ихней версии, 1.1.8).

 

Мост из комплекта Sun Java 2 теперь можно настроить на нужную кодировку. Это делается добавлением дополнительного свойства charSet в набор параметров, передаваемых для открытия соединения с базой. По умолчанию используется file.encoding.

 

Если же Вы свободны в формировании формата - тогда вс± проще. Используйте формат Unicode или UTF8 - и проблем не будет.

 

В случае с БД, можно, конечно, использовать и какой-нибудь 16-ричный формат, но это не всегда приемлемо, т.к. Вы получите 2-х - 4-х кратный рост места на диске и потеряете возможность использовать стандартные программы работы с БД, например генераторы отч±тов.

 

Русские буквы в исходниках Java-программ.

 

Как уже упоминалось, при выполнении программы используется Unicode. Исходные же файлы пишутся в обычных редакторах. Я пользуюсь Far-ом, у Вас, наверняка есть свой любимый редактор. Эти редакторы сохраняют файлы в 8-битовом формате, а значит, что к этим файлам также применимы рассуждения, аналогичные привед±нным выше. Разные версии компиляторов немного по разному выполняют преобразование символов. В версиях JDK 1.1.x используется настройка file.encoding, которую можно поменять при помощи нестандартной опции -J. В Java 2 был введ±н дополнительный параметр -encoding, при помощи которого можно указать используемую кодировку. В скомпилированных классах строки представленны в виде Unicode (точнее в формате UTF8), так что самое интересное происходит при компиляции. Поэтому, самое главное - выяснить, в какой кодировке у Вас исходники и указать правильное значение при компиляции. По умолчанию будет использован вс± тот же пресловутый file.encoding. Пример вызова компилятора:

 

Для JDK 1.1: javac -J-Dfile.encoding=KOI8_R ...

Для JDK 1.2: javac -encoding=KOI8_R ...

Кроме использования этой настройки есть ещ± один метод - указывать буквы в формате "\uxxxx", где указывается код символа. Этот метод работает со всеми версиями, а для получения этих кодов можно использовать стандартную утилиту native2ascii.

 

Русские буквы в файлах properties.

 

Для чтения файлов properties используются методы загрузки ресурсов, которые работают специфичным образом. Собственно для чтения используется метод Properties.load, который не использует file.encoding (там кодировка ж±стко указана), поэтому единственный способ указать русские буквы - использовать формат "\uxxxx" и утилиту native2ascii.

 

Метод Properties.save работает по разному в версиях JDK 1.1 и 1.2. В версиях 1.1 он просто отбрасывал старший байт, поэтому правильно работал только с англицкими буквами. В 1.2 делается обратное преобразование в "\uxxxx", так что он работает зеркально к методу load.

 

Русские буквы в Servlet-ах.

 

Ну, для чего эти самые Servlet-ы нужны, я думаю Вы в курсе. Если нет - то лучше сначала прочитать документацию. Здесь же рассказывается только об особенностях работы с русскими буквами.

 

Так в ч±м же особенности? Когда Servlet посылает ответ клиенту, есть два способа послать этот ответ - через OutputStream (getOutputStream()) или через PrintWriter (getWriter()). В первом случае Вы записываете массивы байтов, поэтому применимы вышеописанные методы записи в файл. В случае же PrintWriter, он сам разбирается с кодировками. В любом случае необходимо правильно указать используемую кодировку при вызове метода setContentType(), для того чтобы клиентский броузер правильно отобразил результат. Прич±м, в случае PrintWriter, это указание должно быть сделанно перед вызовом getWriter(), для того, чтобы было правильное преобразование символов на стороне сервера. Пример:

 

 public void doPost(HttpServletRequest request,HttpServletResponse response)

    throws ServletException, IOException

 {

  response.setContentType("text/html; charset=windows-1251");

 

  PrintWriter out = response.getWriter();

 

  // Отладочный вывод названия кодировки для проверки

  out.println( "Encoding: " + response.getCharacterEncoding() );

  ...

  out.close();

В общем, опыт в написании Servlet-ов у меня небольшой, так что Ваши замечания будут приветствоваться.

GUI

 

Многие связывают неправильный вывод русских букв с неправильной установкой шрифта. Мне кажется, это связанно с тяжким опытом программирования на Windows 3.x, где основная причина действительно была в этом. В Java вс± сложнее и редко действительно связанно со шрифтами. Я не разбирался со специфическими настройками броузеров, т.к. ещ± не писал апплетов, только приложения, но думаю в последних версиях в этом плане вс± нормально.

 

Где же действительно лежат наибольшие подводные камни? В основном это связанно с неправильной перекодировкой символов. Часть этих проблем и методы их решения описанны выше. Если у Вас все преобразования выполняются корректно, и для вывода используется шрифт Unicode, то есть очень большой шанс, что Ваша программа будет работать правильно.

 

Если проблемы вс± же остались, тут нужно выяснить, где они возникают. Попробуйте запустить приложение под разными JVM, под разными платформами, на разных броузерах.

 

Если программа не работает нигде - значит проблема только в ней и в ваших руках. Внимательно перечитайте вс± что было написанно выше и ищите. Если же проблема проявляется только в конкретном окружении - значит дело, скорей всего в настройках. Где именно - зависит от того, какой графической библиотекой Вы пользуетесь. Если AWT - помочь может правильная настройка файла font.properties.ru. Пример корректного файла можно взять из Java 2. Если у Вас нет этой версии, можете скачать его с данного сайта: font.properties.ru (версия для Windows). Если у Вас установлена руссая версия OS - просто добавьте этот файл туда, где лежит файл font.properties. Если же это англицкая версия, то нужно переписать его вместо font.properties. Этот файл зада±т используемые шрифты и кодовые страницы.

 

С библиотекой Swing вс± проще - в ней вс± рисуется через подсистему Java2D. Единственная проблема, с которой я столкнулся - в версиях JDK 1.2 и 1.2.1 русские буквы не выводятся под Win9x из-за ошибки в Java2D. Это ошибка зарегистрированна в BugParade под номером 4192443, так что, будем надеятся, она вс± таки будет исправлена. Как выход, можно скачать версию Swing для JDK 1.1 и запускать приложение из под Microsoft JVM - там вс± выводится корректно. Только не забудьте обновить MS JVM - те версии, что идут в комплекте с IE 4.x не совсем корректно работают. С сервера Microsoft можно скачать свежую версию, например 5.00.3167 - с ней вс± ОК.

 

Кстати, по поводу MS JVM. Непонятно по каким соображениям, но в ней отсутствуют все файлы кодировок русских букв, акромя Cp1251. Если Вам нужна, например, кодировка Cp866, то можно сделать такой финт ушами - выдрать соответствующие файлы из старого комплекта JDK 1.1 и добавить их в CLASSPATH. Найти их, правда, довольно трудно - у Sun-а уже давно изменилась их структура, поэтому последнии версии классов с Microsoft-ом не стыкуются. У меня остались эти классы, годовалой давности (можете скачать их отсюда), но, например, кодировка KOI_8 там с ошибками. Если кто-нибудь возьм±т на себя труд по декомпиляции и поиску ошибок - отошлите мне исправленный класс, я его добавлю.

 

I18n (вывод чисел, дат и т.п.)

 

Загадочная комбинация i18n расшифровывается просто - это сокращение от могучего слова Internationalization. 18 - это кол-во букв между i и n. Означает оно, в контексте Java, возможность автоматической подстройки программы под текущий язык и специфику страны. Делается это через использование класса Locale, представляющего язык и конкретную страну, и классов, которые знают, что с этим Locale делать. Большинство этих классов находятся в пакете java.text.

 

Основной класс, которым пользуются все остальные, - это java.util.ResourceBundle, который позволяет загружать различные виды ресурсов. Прич±м имя загружаемого класса или файла properties зависит от указанного Locale (или Locale по умолчанию - если ничего не указанно).

 

Имя искомого файла формируется при помощи добавления идентификатора языка и страны к имени ресурса. Например, если грузится ресурс resfile, а текущий Locale - ru_RU, то поиск его будет идти в следующем порядке:

 

resfile_ru_RU.class

resfile_ru_RU.properties

resfile_ru.class

resfile_ru.properties

resfile.class

resfile.properties

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

 

Что касается дат, то само форматирование выполняется классом DateFormat. Получить формат, уже настроеный на язык и страну можно при помощи методов getDateInstance(), getTimeInstance() и getDateTimeInstance(). В качестве аргумента можно указать одну из констант для задания необходимого стиля формата. По умолчанию будет использован предпочтительный стиль для данного Locale. Допустимые константы:

 

КонстантаОписаниеПример

SHORTПолностью цифровой, короткий вывод25.01.99 или 17:40

MEDIUMВывод средней длины25.01.1999

LONGДлинный вывод25 Январь 1999 г. или 17:23:32

FULLВся информацияПонедельник, 25 января 1999 г. или 17:23:32 GMT+03:00

Пример:

// Вывод пользователю текущей даты

DateFormat df = DateFormat.getDateInstance();

String s = df.format(new Date());

...

// Вывод текущего времени без секунд

DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);

String s = df.format(new Date());

Если же вы хотите сами контролировать набор выводимых полей и их разделители, то для этого можно использовать класс SimpleDateFormat.

 

Аналогичным образом делается и форматирование чисел. За это отвечает класс NumberFormat. Получить форматы можно при помощи методов getInstance(), getNumberInstance(), getCurrencyInstance() и getPercentInstance(). Свой формат можно сконструировать при помощи класса DecimalFormat.

Домен продается

Популярное

Не так давно в сети появился новый сервис, под названием Dead Man Zero. Этот сервис сделал...
Рынок социальных площадок уже давно стал стабильным. Несмотря на то, что время от времени...
Artisteer 4 – единственный в своем роде продукт, позволяющий автоматизировать работу над созданием...
Октябрь 2018 (14)
Февраль 2017 (3)
Январь 2017 (1)
Август 2016 (1)
Май 2016 (2)
Ноябрь 2015 (1)

Карта сайта: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41

Друзья сайта



Случайная цитата

Роберт Кийосаки:

"Когда люди спрашивают: «С чего мне начинать?» или «Как быстро разбогатеть?», мой ответ их часто разочаровывает. Я просто говорю им то, что когда-то говорил мне богатый папа: «Если хочешь быть богатым, нужно быть финансово грамотным»."

Опрос

Ваша техника?

Настольный компютер
Ноутбук
Смартфон
iPad
iPhone
другое