3. Файлы и потоки данных
Так же как и байты концептуально отделены от символов, в 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).