Файлы данных, потоки, БД.
Итак, как все, надеюсь, знают, в языке Java для представления символов используется Unicode, т.е. по два байта на один символ (тип char размером в 16 бит). В набор символов входят всевозможные буквы со всякими чёрточками и припендюльками, греческие, математические и символы псевдографики. В том числе и так любимые нами символы кириллицы (диапазон значений 0x0400-0x04ff). Так что с этой стороны никакой дискриминации нет.
Если Вам интересны конкретные кода символов, для их просмотра удобно использовать программу "Таблица символов" из WinNT. Вот, например, диапазон кириллицы:
С другой стороны большинство файлов данных основано на 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), и в старых JDK её можно изменить установкой системного свойства file.encoding. Вообще-то, как утверждают в Sun, это свойство отражает системную кодировку, и она не должна изменяться в командной строке (см., например, комментарии к BugID ) Эта кодировка используется тогда, когда явно не указанно название страницы. Т.к. эта настройка одна на все преобразования, иногда можно наткнуться на неприятности. Например, эта же настройка используется для вывода на консольный экран, что, в случае виндов, как правило, неприемлемо - там нужно использовать страницу Cp866. Было бы здорово, если бы эти кодировки указывались независимо - например, console.encoding и т.п., но, думаю, Sun-овцам пока не до таких высоких материй.
Кстати, о выводе на консоль. Есть два пути решения вышеуказанной проблемы:
- Использовать вместо System.out.println свой класс, а уже в нём делать преобразование. Например:
- Написать свою версию PrintStream, поддерживающую нужную кодировку, и подставить его через System.setOut() и System.setErr(). Вот, например, обычное начало в моих программах:
- Читать и записывать массивы байтов (byte[]), а для перекодировки использовать упомянутые методы класса String. Этот способ особенно удобен, когда в потоке могут присутствовать данные в разных кодировках.
- Использовать классы InputStreamReader и OutputStreamWriter из пакета java.io, специально предназначенные для этих целей.
- Сделать преобразование в нужную кодировку. Если вы всё сделаете корректно, то данные не потеряются, хотя, конечно, пользоваться этим желательно только в крайнем случае. Пример:
- Настроить драйвер БД на нужную кодировку. Как именно - это зависит от конкретного драйвера. К сожалению, многие драйвера понятия не имеют о каких-то там кодировках. Иногда их можно пропатчить на этот счёт, но чаще всего приходится действовать обходными путями.
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("Сообщение");
... 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); } ...
Куда же податься бедному российскому программисту? Не отчаивайтесь, есть множество путей прочитать и сохранить данные из файлов в нужной Вам кодировке.
// Чтение русских букв в кодировке 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);
Более подробно о этом методе, и о возможных проблемах с ним, расписано ниже, в разделе О методе перекодировки символов.
Например, один из самых часто используемых драйверов - мост JDBC-ODBC. В версиях JDK 1.1, этот мост просто игнорировал кодировки символов, из-за чего нужно было предпринять дополнительные ухищрения, типа описанных в предыдущем пункте (это также касается и последней ихней версии, 1.1.8).
Мост из комплекта Sun Java 2 теперь можно настроить на нужную кодировку. Это делается добавлением дополнительного свойства charSet в набор параметров, передаваемых для открытия соединения с базой. По умолчанию используется file.encoding. Делается это примерно так: // Параметры соединения с базой Properties connInfo = new Properties(); connInfo.put("user", username); connInfo.put("password", password); connInfo.put("charSet", "Cp1251"); // Устанавливаем соединение Connection db = DriverManager.getConnection(dataurl, connInfo);
Другой пример - драйвер JDBC-OCI (не pure Java - тот называется thin) от Oracle 8.0.5 под Linux. При получении данных из БД, драйвер определяет "свою" кодировку при помощи переменной окружения NLS_LANG. Если эта переменная не найдена, то он считает что кодировка - ISO88591. Весь фокус в том, что NLS_LANG должна быть именно переменной окружения, а properties (типа file.encoding) здесь "не катят". В случае использования драйвера внутри servlet engine Apache+Jserv, переменную окружения можно задать в файле jserv.properties: wrapper.env=NLS_LANG=American_America.CL8KOI8R
Информацию об этом прислал , за что ему отдельное спасибо.
Если же Вы свободны в формировании формата - тогда всё проще. Используйте формат Unicode или UTF8 - и проблем не будет.
В случае с БД, можно, конечно, использовать и какой-нибудь 16-ричный формат, но это не всегда приемлемо, т.к. Вы получите 2-х - 4-х кратный рост места на диске и потеряете возможность использовать стандартные программы работы с БД, например генераторы отчётов.