46375 (607825), страница 2
Текст из файла (страница 2)
ISO-IR-144
8859_5
Cyrillic
CSISOLatinCyrillic
IBM915
IBM-915
Cp915
915
Причём синонимы, в отличии от основного имени нечувствительны к регистру символов - такова особенность реализации.
Стоит отметить, что эти кодировки на некоторых JVM могут отсутствовать. Например, с сайта Sun можно скачать две разные версии JRE - US и International. В US версии присутствует только минимум - ISO-8859-1, ASCII, Cp1252, UTF8, UTF16 и несколько вариаций двухбайтового Unicode. Всё прочее есть только в International варианте. Иногда из-за этого можно нарваться на грабли с запуском программы, даже если ей не нужны русские буквы. Типичная ошибка, возникающая при этом:
Error occurred during initialization of VM
java/lang/ClassNotFoundException: sun/io/ByteToCharCp1251
Возникает она, как не трудно догадаться, из-за того, что JVM, исходя из русских региональных настроек пытается установить кодировку по умолчанию в Cp1251, но, т.к. класс поддержки таковой отсутствует в US версии, закономерно обламывается.
Файлы и потоки данных
Так же как и байты концептуально отделены от символов, в Java различаются потоки байтов и потоки символов. Работу с байтами представляют классы, которые прямо или косвенно наследуют классы InputStream или OutputStream (плюс класс-уникум RandomAccessFile). Работу с символами представляет сладкая парочка классов Reader/Writer (и их наследники, разумеется).
Для чтения/записи не преобразованных байтов используются потоки байтов. Если известно, что байты представляют собой только символы в некоторой кодировке, можно использовать специальные классы-преобразователи InputStreamReader и OutputStreamWriter, чтобы получить поток символов и работать непосредственно с ним. Обычно это удобно в случае обычных текстовых файлов или при работе с многими сетевыми протоколами Internet. Кодировка символов при этом указывается в конструкторе класса-преобразователя. Пример:
// Строка Unicode
String string = "...";
// Записываем строку в текстовый файл в кодировке Cp866
PrintWriter pw = new PrintWriter // класс с методами записи строк
(new OutputStreamWriter // класс-преобразователь
(new FileOutputStream // класс записи байтов в файл
("file.txt"), "Cp866");
pw.println(string); // записываем строку в файл
pw.close();
Если в потоке могут присутствовать данные в разных кодировках или же символы перемешаны с прочими двоичными данными, то лучше читать и записывать массивы байтов (byte[]), а для перекодировки использовать уже упомянутые методы класса String. Пример:
// Строка Unicode
String string = "...";
// Записываем строку в текстовый файл в двух кодировках (Cp866 и Cp1251)
OutputStream os = new FileOutputStream("file.txt"); // класс записи байтов в файл
// Записываем строку в кодировке Cp866
os.write( string.getBytes("Cp866") );
// Записываем строку в кодировке Cp1251
os.write( string.getBytes("Cp1251") );
os.close();
Консоль в Java традиционно представлена потоками, но, к сожалению, не символов, а байтов. Дело в том, что потоки символов появились только в JDK 1.1 (вместе со всем механизмом кодировок), а доступ к консольному вводу/выводу проектировался ещё в JDK 1.0, что и привело к появлению уродца в виде класса PrintStream. Этот класс используется в переменных System.out и System.err, которые собственно и дают доступ к выводу на консоль. По всем признакам это поток байтов, но с кучей методов записи строк. Когда Вы записываете в него строку, внутри происходит конвертация в байты с использованием кодировки по умолчанию, что в случае виндов, как правило, неприемлемо - кодировка по умолчанию будет Cp1251 (Ansi), а для консольного окна обычно нужно использовать Cp866 (OEM). Эта ошибка была зарегистрированна ещё в 97-ом году (4038677) но Sun-овцы исправлять её вроде не торопятся. Так как метода установки кодировки в PrintStream нет, для решения этой проблемы можно подменить стандартный класс на собственный при помощи методов System.setOut() и System.setErr(). Вот, например, обычное начало в моих программах:
...
public static void main(String[] args)
{
// Установка вывода консольных сообщений в нужной кодировке
try
{
String consoleEnc = System.getProperty("console.encoding","Cp866");
System.setOut(new CodepagePrintStream(System.out,consoleEnc) );
System.setErr(new CodepagePrintStream(System.err,consoleEnc) );
}
catch(UnsupportedEncodingException e)
{
System.out.println("Unable to setup console codepage: " + e);
}
...
Исходники класса CodepagePrintStream Вы можете найти на данном сайте: CodepagePrintStream.java.
Если Вы сами конструируете формат данных, я рекомендую Вам использовать одну из многобайтовых кодировок. Удобнее всего обычно формат UTF8 - первые 128 значений (ASCII) в нём кодируются одним байтом, что часто может значительно уменьшить общий объём данных (не зря эта кодировка принята за основу в мире XML). Но у UTF8 есть один недостаток - кол-во требуемых байтов зависит от кода символов. Там, где это критично можно использовать один из двухбайтовых форматов Unicode (UnicodeBig или UnicodeLittle).
Базы данных
Для того, чтобы прочитать корректно символы из БД, обычно достаточно указать JDBC-драйверу на нужную кодировку символов в БД. Как именно - это зависит от конкретного драйвера. Сейчас уже многие драйвера поддерживают подобную настройку, в отличии от недавнего прошлого. Далее приведены несколько известных мне примеров.
Мост JDBC-ODBC
Это один из самых часто используемых драйверов. Мост из JDK 1.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 от Oracle 8.0.5 под Linux
При получении данных из БД, данный драйвер определяет "свою" кодировку при помощи переменной окружения NLS_LANG. Если эта переменная не найдена, то он считает что кодировка - ISO-8859-1. Весь фокус в том, что NLS_LANG должна быть именно переменной окружения (устанавливаемой командой set), а не системное свойство Java (типа file.encoding). В случае использования драйвера внутри servlet engine Apache+Jserv, переменную окружения можно задать в файле jserv.properties:
wrapper.env=NLS_LANG=American_America.CL8KOI8R
Информацию об этом прислал Сергей Безруков, за что ему отдельное спасибо.
Драйвер JDBC-thin от Oracle
Сей драйвер вроде как не требует особой настройки. По крайней мере в документации об этом ни слова. По всей видимости у Oracle в протоколе обмена ходит нормальный Unicode, правда за исключением составных типов (типы Object и Collection). Если Вы пользуетесь сложными типами, то не забудьте про отдельный zip с поддержкой кодировок (с именем типа nls_charset12.zip), который скачивается отдельно. В противном случае драйвер будет поддерживать только минимум (US7ASCII, WE8DEC, WE8ISO8859P1 и UTF8) и, если БД была создана в другой кодировке, то при получении строковых значений из составных типов будет сплошной 16-ричный мусор (если включён log у DriverManager, то при этом будет видна ругань на неизвестную кодировку).
Самая большая проблема, с которой многие сталкиваются - некорректная кодировка сообщений об ошибках. Дело в том, что оригинальные драйвера от Oracle 8.1.7 и 9.0.1 содержат некорректно сформированные файлы ресурсов с текстами русских сообщений. Драйвера от 9.0.2 и 9.2.x уже нормальные. Эти файлы ресурсов можно довольно легко пропатчить при помощи утилиты native2ascii. Готовый скрипт патча можно найти здесь: http://java.ksu.ru/sources/oracle/ (или здесь - oracle_jdbc_repair.bat). Можно также поменять текущий язык сессии выполнив команду "ALTER SESSION SET NLS_LANGUAGE=English". При этом сообщения станут выдаваться на английском. Ну а самый правильный путь - использовать последние версии драйверов от 9.2.x, благо что они совместимы со старыми версиями Oracle и к тому же содержат исправления других ошибок.
Драйвер JDBC для работы с DBF (com.hxtt.sql.dbf.DBFDriver, бывший zyh.sql.dbf.DBFDriver)
Данный драйвер только недавно научился работать с русскими буквами. Настройка выполняется немного по разному в зависимости от версии драйвера Версии Beta 5.4 (от 02.04.2002) и более поздние уже нормально воспринимают настройку charSet. В версиях Beta 5.2 (от 30.07.2001) и Beta 5.3 (30.11.2001), хоть он и сообщает по getPropertyInfo() что он понимает свойство charSet - это фикция. Реально же настроить кодировку можно установкой свойства CODEPAGEID. Для русских символов там доступны два значения - "66" для Cp866 и "C9" для Cp1251. Пример:
// Параметры соединения с базой
Properties connInfo = new Properties();
// Кодировка Cp866
connInfo.put("charSet", "Cp866");
connInfo.put("CODEPAGEID", "66");
// Устанавливаем соединение
Connection db = DriverManager.getConnection("jdbc:DBF:/C:/MyDBFFiles", connInfo);
Если у Вас DBF-файлы формата FoxPro, то у них своя специфика. Дело в том, что FoxPro сохраняет в заголовке файла ID кодовой страницы (байт со смещением 0x1D), которая использовалась при создании DBF-ки. При открытии таблицы драйвер использует значение из заголовка, а не параметр "CODEPAGEID" (параметр в этом случае используется только при создании новых таблиц). Соответственно, чтобы всё работало нормально, DBF-файл должен быть создан с правильной кодировкой - иначе будут проблемы.
MySQL (org.gjt.mm.mysql.Driver)
С этим драйвером тоже всё довольно просто:
// Параметры соединения с базой
Properties connInfo = new Properties();
connInfo.put("user",user);
connInfo.put("password",pass);
connInfo.put("useUnicode","true");
connInfo.put("characterEncoding","KOI8_R");
Connection conn = DriverManager.getConnection(dbURL, props);
InterBase (interbase.interclient.Driver)
Для этого драйвера работает параметр "charSet":
// Параметры соединения с базой
Properties connInfo = new Properties();
connInfo.put("user", username);
connInfo.put("password", password);
connInfo.put("charSet", "Cp1251");
// Устанавливаем соединение
Connection db = DriverManager.getConnection(dataurl, connInfo);
Однако не забудьте при создании БД и таблиц указать кодировку символов. Для русского языка можно использовать значения "UNICODE_FSS" или "WIN1251". Пример:
CREATE DATABASE 'E:\ProjectHolding\DataBase\HOLDING.GDB' PAGE_SIZE 4096
DEFAULT CHARACTER SET UNICODE_FSS;
CREATE TABLE RUSSIAN_WORD
(
"NAME1" VARCHAR(40) CHARACTER SET UNICODE_FSS NOT NULL,
"NAME2" VARCHAR(40) CHARACTER SET WIN1251 NOT NULL,
PRIMARY KEY ("NAME1")
);
В версии 2.01 InterClient присутствует ошибка - классы ресурсов с сообщениями для русского языка там неправильно скомпилированы. Скорей всего разработчики просто забыли указать кодировку исходников при компиляции. Есть два пути исправления этой ошибки:
Использовать interclient-core.jar вместо interclient.jar. При этом русских ресурсов просто не будет, и автоматом подхватятся английские.
Перекомпилировать файлы в нормальный Unicode. Разбор class-файлов - дело неблагодарное, поэтому лучше воспользоваться JAD-ом. К сожалению JAD, если встречает символы из набора ISO-8859-1, выводит их в 8-иричной кодировке, так что воспользоваться стандартным перекодировщиком native2ascii не удастся - придётся написать свой (программа Decode). Если Вам не хочется заморачиваться с этими проблемами - можете просто взять готовый файл с ресурсами (пропатченый jar с драйвером - interclient.jar, отдельные классы ресурсов - interclient-rus.jar).
JayBird (org.firebirdsql.jdbc.FBDriver)
Для этого драйвера указание кодировки отличается от InterClient:
// Параметры соединения с базой
Properties connInfo = new Properties();
connInfo.put("user", username);
connInfo.put("password", password);
connInfo.put("lc_ctype","WIN1251");
// Устанавливаем соединение
Connection db = DriverManager.getConnection(dataurl, connInfo);
SAP DB (com.sap.dbtech.jdbc.DriverSapDB)
Для этого драйвера можно включить использование Unicode задав параметр unicode в true. В противном случае будет использованна жёстко зашитая ISO-8859-1, с вытекающими отсюда осточертевшими "??????". Кроме того существует доработанная напильником версия (sapdbc.zip), которая позволяет задавать параметр charSet.
DataSource (javax.sql.DataSource)
Если получение соединение производится через использование DataSource, то поддержку настройки используемой кодировки должна содержать конкретная реализация, которая регистрируется в JNDI. Для примера можно взять реализацию от InterClient (interbase.interclient.DataSource). В этом классе есть метод setCharSet(), используя который можно указать необходимую кодировку. Пример:
interbase.interclient.DataSource dataSource = new interbase.interclient.DataSource();
dataSource.setServerName("pongo");
dataSource.setDatabaseName("/databases/employee.gdb");
dataSource.setCharSet("Cp1251");
javax.naming.Context context = new javax.naming.InitialContext();
context.bind("jdbc/EmployeeDB", dataSource);
Другой пример - реализация от DBF-драйвера (com.hxtt.sql.HxttDataSource):