В операционной системе Windows по умолчанию file.

encoding=Сp1251. Однако существует еще одно свойство console.encoding, которое указывает, в какой кодировке следует производить вывод на консоль. file.encoding указывает Java машине в какой кодировке следует считывать исходные коды программ, если кодировка не указана пользователем при компиляции. Фактически, данное системное свойство распространяется и на вывод с помощью System.out.println().
По умолчанию данное свойство не установлено. Эти системные свойства можно устанавливать и в вашей программе, однако, для нее это уже будет не актуально, поскольку виртуальная машина пользуется теми значениями, которые были считаны перед компиляцией и запуском вашей программы. Кроме того, как только ваша программа отрабатывает, системные свойства восстанавливаются. В этом можно убедиться, запустив дважды следующую программу.
/**
* @author <a href="mailto:zagrebin_v@mail.ru"> Victor Zagrebin </a>
*/
public class SetPropertyDemo
{
public static void main(String[] args)
{
System.out.println("file.encoding before="+System.getProperty("file.encoding"));
System.out.println("console.encoding before="+System.getProperty("console.encoding"));
System.setProperty("file.encoding","Cp866");
System.setProperty("console.encoding","Cp866");
System.out.println("file.encoding after="+System.getProperty("file.encoding"));
System.out.println("console.encoding after="+System.getProperty("console.encoding"));
}
}
Установка данных свойств в программе необходима в тех случаях, когда оно используется в последующем коде до завершения работы программы.
Воспроизведем еще ряд типичных примеров, с проблемами, которыми сталкиваются программисты при выводе. Допустим, у нас есть следующая программа:
public class CyryllicDemo
{
public static void main(String[] args)
{
String s1 = "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ";
String s2 = "абвгдежзийклмнопрстуфхцчшщьыъэюя";
System.out.println(s1);
System.out.println(s1);
}
}
Кроме этой программы будем оперировать дополнительными факторами:
команда компиляции;
команда запуска;
кодировка исходного кода программы (устанавливается в большинстве текстовых редакторов);
кодировка вывода на консоль (Используется Cp866 по умолчанию или устанавливается с помощью команды chcp) ;
видимый вывод в окне консоли.
javac CyryllicDemo.java
java CyryllicDemo
Кодировка файла: Cp1251
Кодировка консоли: Cp866
Вывод:
└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘▄█┌▌▐▀
рстуфхцчшщъыьэюяЁёЄєЇїЎў°∙№√·¤■
javac CyryllicDemo.java
Кодировка файла: Cp866
Кодировка консоли: Cp866
Вывод:
CyryllicDemo.java:5: warning: unmappable character for encoding Cp1251
String s1 = "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧ?ЩЬЫЪЭЮЯ";
^
javac CyryllicDemo.java -encoding Cp866
java -Dfile.encoding=Cp866 CyryllicDemo
Кодировка файла: Cp866
Кодировка консоли: Cp866
Вывод:
АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ
абвгдежзийклмнопрстуфхцчшщьыъэюя
javac CyryllicDemo.java -encoding Cp1251
java -Dfile.encoding=Cp866 CyryllicDemo
Кодировка файла: Cp1251
Кодировка консоли: Cp866
Вывод:
АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ
абвгдежзийклмнопрстуфхцчшщьыъэюя
Особо следует обратить внимание на проблему “Куда делась буква Ш ?” из второй серии запусков. Еще внимательнее к этой проблеме следует относиться, если вы заранее не знаете, какой текст будет храниться в выводимой строке, и по наивности сделаете компиляцию без задания кодировки. Если у вас действительно в строке не будет буквы Ш, то компиляция выполнится удачно и запуск тоже. И на этом вы даже забудете, что упускаете мелочь (букву Ш), которая может потенциально встретиться в выводимой строке и неизбежно приведет к дальнейшим ошибкам.
В третьей и четвертой серии при компиляции и запуске используются ключи: -encoding Cp866 и -Dfile.encoding=Cp866. Ключ -encoding указывает, в какой кодировке следует считывать файл с исходным кодом программы. Ключ -Dfile.encoding=Cp866 указывает в какой кодировке следует производить вывод.
Unicode префикс \u и русские символы в исходных кодах
В языке Java для записи символов в формате Unicode существует специальный префикс \u, а следующие за ним четыре шестнадцатиричные цифры задают сам символ. Например, \u2122 – это символ торговой марки (™). Такая форма записи выражает символ любого алфавита с помощью цифр и префикса – символов, которые содержатся в стабильном диапазоне кодов от 0 до 127, который не затрагивается при перекодировке исходного кода. И, теоретически, в приложении или аплете на языке Java можно использовать любой символ в формате Unicode, однако будет ли он отображаться корректно на экране дисплея и, будет ли он отображаться вообще, зависит от многих факторов. Для аплетов имеет значение тип браузера, а для программ и аплетов имеет значение тип операционной системы и кодировка, в которой написан исходный код программы.
Например, на компьютерах, работающих под управлением американской версии системы Windows, с помощью языка Java невозможно отобразить японские иероглифы из-за вопроса интернационализации.
В качестве второго примера можно привести вполне частую ошибку программистов. Многие думают, что задание русских символов в Unicode формате с помощью префикса \u в исходном коде программы может решить проблему вывода русских символов при любых обстоятельствах. Ведь виртуальная машина Java переводит исходный код программы в Unicode. Однако прежде чем осуществить перевод в Unicode виртуальная машина должна знать, в какой кодировке написан исходный код вашей программы. Ведь вы можете написать программу и в кодировке Cp866 (DOS) и в Сp1251 (Windows), что типично для данной ситуации. В том случае, если вы не указали кодировки, виртуальная машина Java считывает ваш файл с исходным кодом программы в той кодировке, которая указана в системном свойстве file.encoding.
Однако вернемся к параметрам по умолчанию, и будем считать что file.encoding=Сp1251, а вывод на консоль производится в Cp866. Вот именно в этом случае выходит следующая ситуация: допустим у вас есть файл в кодировке Сp1251:
Файл MsgDemo1.java
public class MsgDemo1
{
public static void main(String[] args)
{
String s = "\u043A\u043E"+
"\u0434\u0438\u0440\u043E\u0432"+
"\u043A\u0430";
System.out.println(s);
}
}
И вы ожидаете, что на консоль выведется слово “кодировка”, однако получаете:
Дело в том, что коды с префиксом \u, перечисленные в программе действительно кодируют нужные кириллические символы в кодовой таблице Unicode, однако они рассчитаны на то, что исходный код вашей программы будет считан в кодировке Cp866 (DOS). По умолчанию, же в системных свойствах указана кодировка Сp1251 (file.encoding=Сp1251). Естественно, первое и неправильное, что приходит на ум – поменять кодировку файла с исходным кодом программы в текстовом редакторе. Но это ни к чему не приведет. Виртуальная машина Java все равно будет считывать ваш файл в кодировке Сp1251, а коды \u рассчитаны на Cp866.
Из этой ситуации есть три выхода. Первый вариант – использовать ключ -encoding на этапе компиляции и –Dfile.encoding на этапе запуска программы. В этом случае вы принудительно указываете виртуальной машине Java, чтобы она считывала файл с исходным кодом в заданной кодировке и выводила в заданной кодировке.
Как видно из вывода на консоли, при компиляции должен задаваться дополнительный параметр –encoding Cp866, а при запуске должен задаваться параметр –Dfile.encoding=Cp866.
Второй вариант заключается в том, чтобы перекодировать символы в самой программе. Он предназначен для восстановления верных кодов букв, если они были неверно проинтерпретированы. Суть метода проста: из полученных неверных символов, используя соответствующую кодовую страницу, восстанавливается исходный массив байтов. Затем из этого массива байтов, используя уже корректную страницу, получаются нормальные коды символов.
Для преобразования потока байт byte[] в строку String и обратно, в классе String есть следующие возможности: конструктор String(byte[] bytes, String enc), который получает на вход поток байт с указанием их кодировки; если кодировку опустить, то она будет принята кодировка по умолчанию из системного свойства file.encoding. Метод getBytes(String enc) возвращает поток байт, записанных в указанной кодировке; кодировку также можно опустить и будет принята кодировка по умолчанию из системного свойства file.encoding.
Пример:
Файл MsgDemo2.java
import java.io .UnsupportedEncodingException;
public class MsgDemo2
{
public static void main(String[] args) throws UnsupportedEncodingException
{
String str = "\u043A\u043E"+
"\u0434\u0438\u0440\u043E\u0432"+
"\u043A\u0430";
byte[] b = str.getBytes("Cp866");
String str2 = new String(b,"Cp1251");
System.out.println(str2);
}
}
Результат вывода программы:
Этот способ является менее гибким в том случае, если вы ориентируетесь на то, что кодировка в системном свойстве file.encoding не изменится. Однако этот способ может стать самым гибким, если проводить опрос системного свойства file.encoding, и подставлять полученное значение кодировки при формировании строк в вашей программе. При использовании данного способа следует внимательно относиться к тому, что не все страницы выполняют однозначное преобразование byte <-> char.
Третий способ заключается в том, чтобы подобрать корректные Unicode коды для вывода слова “кодировка” из расчета, что файл будет считываться в кодировке по умолчанию – Cp1251. Для этих целей существует специальная утилита native2ascii.
Эта утилита входит в состав Sun JDK и предназначена для преобразования исходных текстов к ASCII-виду. При запуске без параметров, работает со стандартным входом (stdin), а не выводит подсказку по ключам, как остальные утилиты. Это приводит к тому, что многие и не догадываются о необходимости указания параметров (кроме, тех, кто заглянул в документацию). Между тем этой утилите для правильной работы необходимо, как минимум, указать используемую кодировку с помощью ключа -encoding. Если этого не сделать, то будет использована кодировка по умолчанию (file.encoding), что может несколько расходится с ожидаемой. В результате, получив неверные кода букв (из-за неверной кодировки), можно потратить много времени на поиск ошибок в абсолютно верном коде.
Следующий скриншот показывает различие в последовательностях Unicode кодов для одного и того же слова, если исходный файл будет считываться в кодировке Cp866 и в кодировке Cp1251.
Таким образом, если вы принудительно не указываете кодировки для виртуальной машины Java при компиляции и при запуске, а кодировкой по умолчанию (file.encoding) является Cp1251, то исходный код программы должен выглядеть следующим образом:
Файл MsgDemo3.java
public class MsgDemo3
{
public static void main(String[] args)
{
String s = "\u0404\u00AE"+
"\u00A4\u0401\u0430\u00AE\u045E"+
"\u0404\u00A0";
System.out.println(s);
}
}
Использовав третий способ, можно сделать вывод: если кодировка файла с исходным кодом в редакторе совпала с кодировкой в системе, то сообщение “кодировка” появится в нормальном виде.
Чтение и запись в файл русских символов, выраженных Unicode префиксом \u
Для чтения данных, записанных как в формате MBCS (используя кодировку UTF-8), так и в формате Unicode, можно использовать класс InputStreamReader из пакета java.io , подставляя в его конструктор различные кодировки. Для записи используется OutputStreamWriter. В описании пакета java.lang написано, что каждая реализация JVM поддерживет следующие кодировки:
Название кодировки Описание
US-ASCII семибитная ASCII, она же ISO646-US, она же основная латинская часть Unicode;
ISO-8859-1 то же самое, что ISO-LATIN-1;
UTF-8 8-битный;
UTF-16BE 16-битный, порядок байт big-endian;
UTF-16LE 16-битный, порядок байт little-endian;
UTF-16 16-битный, порядок байт определяется начальными значениями (допускается любой), на выходе порядок байт big-endian.
Файл WriteDemo.java
import java.io .Writer;
import java.io .OutputStreamWriter;
import java.io .FileOutputStream;
import java.io .IOException;
/**
* Вывод Unicode строки в файл в заданной кодировке.
* @author <a href="mailto:zagrebin_v@mail.ru"> Victor Zagrebin </a>
*/
public class WriteDemo
{
public static void main(String[] args) throws IOException
{
String str = "\u043A\u043E"+
"\u0434\u0438\u0440\u043E\u0432"+
"\u043A\u0430";
Writer out1 = new OutputStreamWriter(new FileOutputStream("out1.txt"), "Cp1251");
Writer out2 = new OutputStreamWriter(new FileOutputStream("out2.txt"), "Cp866");
Writer out3 = new OutputStreamWriter(new FileOutputStream("out3.txt"), "UTF-8");
Writer out4 = new OutputStreamWriter(new FileOutputStream("out4.txt"), "Unicode");
out1.write(str);
out1.close();
out2.write(str);
out2.close();
out3.write(str);
out3.close();
out4.write(str);
out4.close();
}
}
Компиляция:
javac WriteDemo.java
Запуск:
java WriteDemo
В результате выполнения программы должно создаться четыре файла (out1.txt out2.txt out3.txt out4.txt) в каталоге запуска программы, каждый из которых будет содержать слово “кодировка” в разной кодировке, что можно проверить в текстовых редакторах или с помощью просмотра дампа файла.
Следующая программа будет считывать и выводить на экран содержимое каждого из созданных файлов.
Файл ReadDemo.java
import java.io .Reader;
import java.io .InputStreamReader;
import java.io .InputStream;
import java.io .FileInputStream;
import java.io .IOException;
/**
* Чтение Unicode символов из файла в заданной кодировке.
* @author <a href="mailto:zagrebin_v@mail.ru"> Victor Zagrebin </a>
*/
public class ReadDemo
{
public static void main(String[] args) throws IOException
{
String out_enc = System.getProperty("console.encoding","Cp866");
System.out.write(readStringFromFile("out1.txt", "Cp1251", out_enc));
System.out.write('\n');
System.out.write(readStringFromFile("out2.txt", "Cp866", out_enc));
System.out.write('\n');
System.out.write(readStringFromFile("out3.txt", "UTF-8", out_enc));
System.out.write('\n');
System.out.write(readStringFromFile("out4.txt", "Unicode", out_enc));
}
public static byte[] readStringFromFile(String filename,
String file_enc,
String out_enc) throws IOException
{
int size;
InputStream f = new FileInputStream(filename);
size = f.available();
Reader in = new InputStreamReader(f,file_enc);
char ch[] = new char[size];
in.read(ch,0,size);
in.close();
return (new String(ch)).getBytes(out_enc);
}
}
Компиляция:
javac ReadDemo.java
Запуск:
java ReadDemo
Результат вывода программы:
Особо следует отметить использование следующей строки кода в данной программе:
String out_enc = System.getProperty("console.encoding","Cp866");
С помощью метода getProperty осуществляется попытка прочитать значение системного свойства console.encoding, которое задает кодировку, в которой будут выводиться данные на консоль. Если же такое свойство не установлено (зачастую оно не установлено), то переменной out_enc присвоится "Cp866". И далее, переменная out_enc используется там, где необходимо привести считанную из файла строку к кодировке, пригодной для вывода на консоль.
Также возникает закономерный вопрос: “почему используется System.out.write, а не System.out.println” ? Как было описано выше, системное свойство file.encoding используется не только для считывания исходного файла, а и для вывода с помощью System.out.println, что в данном случае приведет к некорректному выводу.
Неправильное отображение кодировки в программах, ориентированных на web
Программисту прежде всего следует знать: не имеет смысла иметь строку, не зная, какую кодировку она использует. Не существует такого понятия как "простой" (plain) текст в ASCII. Если у вас есть строка, в памяти, в файле, или в сообщении электронной почты, вы должны знать, в какой она кодировке, иначе вы не сможете ее правильно интерпретировать или показать пользователю.
Почти все традиционные проблемы типа "мой вебсайт похож на тарабарщину" или "мои электронные письма не читаются, если я использую символы с ударениями" лежат на ответственности программиста, который не понимает простого факта, что если вы не знаете в какой кодировке строка UTF-8 или ASCII или ISO 8859-1 (Латинский-1) или Windows 1252 (Западноевропейский), вы просто не сможете вывести ее правильно. Есть более ста кодировок символов выше кодовой точки 127, и нет никакой информации для того, чтобы выяснить, какая кодировка нужна. Как мы сохраняем информацию о том, какую кодировку используют строки? Существуют стандартные способы для указания этой информации. Для сообщений электронной почты вы должны поместить в HTTP заголовок строку
Content-Type: text/plain; charset="UTF-8"
Для веб-страницы оригинальная идея была в том, что веб-сервер сам будет посылать HTTP заголовок, перед самой страницей HTML. Но это вызывает определенные проблемы. Предположим, что вы имеете большой веб-сервер с большим количеством сайтов и сотнями страниц, созданных большим количеством людей на огромном количестве различных языков, и все они не используют специфическую кодировку. Сам веб-сервер действительно не может знать, какая кодировка у каждого файла, и поэтому не может послать заголовок с указанием Content-Type. Поэтому для указания правильной кодировки в http заголовке оставалось держать информацию о кодировке внутри html файла путем вставки специального тега. Сервер бы в таком случае считывал наименование кодировки из мета-тега и помещал ее в HTTP заголовок.
Возникает уместный вопрос: «как начать считывать файл HTML, пока Вы не узнаете, какую кодировку он использует?! К счастью, почти все кодировки используют одну и ту же таблицу символов с кодами от 32 до 127, и сам код HTML состоит из этих символов, и вы можете даже не встретить в html файле информацию о кодировке, если он полностью состоит из таких символов. Поэтому, в идеале, тег <meta> с указанием кодировки действительно должен быть в самой первой строке в секции <head>, потому что как только веб-браузер увидит этот признак, он перестанет разбирать страницу и начнет все заново, используя ту кодировку, которую вы задали.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Что делают веб-браузеры, если они не находят никакого Content-Type, ни в заголовке http, ни в теге Meta? Internet Explorer фактически делает кое-что весьма интересное: он пробует распознать кодировку и язык, основываясь на частоте, с которой различные байты появляются в типичном тексте в типичных кодировках различных языков. Так как разные старые 8-байтовые кодовые страницы по-разному размещали национальные символы в диапазоне между 128 и 255, и так как все человеческие языки имеют различные частотные вероятности использования букв, такой подход часто неплохо срабатывает.
Это весьма причудливо, но это, кажется, действительно cрабатывает достаточно часто и наивные авторы веб-страниц, которые никогда не знали, что они нуждались в указании тэга Content-Type в заголовке их страничек для того, чтобы странички правильно отображались, до того прекрасного дня, когда они напишут что-то, что точно не соответствует типичному частотно-вероятностному распределению букв их родного языка, и Internet Explorer решит, что это корейский язык и покажет ее соответствующим образом.
Так или иначе, что остается делать читателю этого вебсайта, который был написан на болгарском языке, но отображается на корейском (и даже не на осмысленном корейском)? Он использует меню View | Encoding и пробует несколько разных кодировок (есть по крайней мере дюжина для восточноевропейских языков), пока картина не станет более ясной. Если, конечно, он знает, как это делать, ведь большинство людей этого не знает.
Стоит отметить, что для UTF-8, которая уже в течение многих лет прекрасно поддерживается веб-браузерами, еще никем не встречена проблема с правильным отображением веб-страниц.
Ссылки:
Joel Spolsky. The absolute minimum every software developer absolutely, positively must know about Unicode and character sets (No Excuses!) 08.10.2003 http://www.joelonsoftware.com/articles/Unicode.html Сергей Астахов. Java: русские буквы и не только…
Сергей Семихатов. Java и Unicode. 08.2000 - 27.07.2005
Хорстман К.С., Корнелл Г. Библиотека профессионала. Java 2. Том 1. Основы. — М.: Издательский дом Вильямс, 2003. — 848 с
Dan Chisholms. Java Programmer Mock Exams. Objective 2, InputStream and OutputStream Reader/Writer. Java Character Encoding: UTF and Unicode. http://www.jchq.net/certkey/1102_12certkey.htm Package java.io . JavaTM 2 Platform Standard Edition 6.0 API Specification. http://java.sun.com Unicode Standard, version 4.0. http://www.unicode.org

Комментарии

Комментариев нет.