Java options file encoding
В прошлой публикации мы установили и настроили Sublime Text в качестве редактора для написания кода на Java.
- о компиляции и запуске Java-программ из консоли
- как выводить в нее корректный русский текст
- способ отображения байт-кода
На первых порах начинающему программисту очень важно научиться выполнять необходимые действия минимальными средствами, чтобы:
- иметь представление, что происходит «под капотом»
- не быть зависимым от среды разработки
- не превратиться в специалиста по нажиманию кнопок в инструментах для профессионалов
- не оказаться беспомощным за их пределами
Вам сейчас нужно максимально набить шишек, находясь в спартанских условиях, которые закалят и научат самостоятельно и оперативно решать возникающие проблемы. Это придаст уверенности и повысит вашу экспертность.
Есть такое понятие — профессиональный кругозор. Он важен для людей любой профессии. Программисты — не исключение. Если человек хочет стать настоящим разработчиком, обладающим фундаментальными знаниями, а не поверхностными и неструктурированными, то ему нужно изучать возможности языка последовательно и системно. Компиляция и запуск — это основа основ, это база, мимо которой никак нельзя пройти.
Компилировать и запускать программы на первых порах мы будем из консоли. В этой и последующих статьях мы будем много работать с этим инструментом. Это важный навык, который используется программистами повсеместно.
Откроем в Sublime Text терминал, нажав Ctrl + Alt + T (при этом файл MyFirstApp.java должен быть открыт в редакторе). Обязательно проверьте, что терминал запустился в папке StartJava (или где вы сохранили класс).
Для компиляции воспользуемся уже знакомой утилитой javac (java compiler, компилятор java). Она нужна для преобразования Java-кода в язык виртуальной машины (байт-код).
Отсутствие каких-либо сообщений свидетельствует о том, что компилятор не нашел ошибок в нашем коде. Результатом его работы стал файл MyFirstApp.class.
Как мы уже знаем, MyFirstApp.class содержит байт-код. Для его исполнения воспользуемся утилитой java. Именно она стартует JVM, которая, в свою очередь, запустит класс MyFirstApp.
Обратите внимание, что необходимо указать не имя файла MyFirstApp.class, а имя класса. При этом писать какое-либо расширение не нужно. Утилита java принимает в качестве параметра именно имя класса, а не имя файла, где он находится.
Мы уже не первый раз упоминаем про байт-код, но до сих пор в глаза его не видели. Исправим эту ситуацию.
Для отображения (декомпиляции) байт-кода класса необходимо в консоли написать команду javap -c MyFirstApp:
Как правило, умение читать и понимать байт-код, вносить в него изменения для рядовых Java-разработчиков обычно не требуется — это очень специфические знания. Так что долго не сидите на этой теме.
Если обобщить все этапы, которые проходит программа перед запуском, то схематично их можно отобразить в виде схемы:
Чтобы посмотреть, как работает программа, необходимо выполнить два действия: компиляцию и запуск. Если эти шаги требуется исполнять часто, то данный процесс рано или поздно надоест. Хотелось бы его упростить.
Удалите из папки class-файл, чтобы все было честно. И запустите программу без компиляции в явном виде, написав java MyFirstApp.java:
Мы только что запустили (и скомпилировали) файл с java-кодом без использования команды javac. При этом исходный код, в любом случае, компилируется в памяти (без создания на диске class-файла), а затем исполняется JVM.
Эта функция ограничена кодом, который хранится в одном файле. Она не сработает для программ, состоящих из двух и более файлов.
Теперь, когда вы умеете компилировать и запускать программу, можете поэкспериментировать с кодом MyFirstApp.java, внося в него изменения. Попробуем удалить точку с запятой.
> java MyFirstApp.java error: 'main' method is not declared 'public static'
Если вы используете Linux или macOS, то дальше можете не читать, т. к. ниже описаны проблемы и их решения для Windows. У вас таких проблем не будет!
Написанная нами программа выводит текст в консоли на английском языке. Как вы думаете, что произойдет, если попробовать вывести текст на русском?
> java MyFirstApp.java Р?апиС?Р°Р?Р? Р?Р?Р?Р°Р?Р?С?, С?Р°Р?Р?С?Р°Р?С? Р?Р?Р·Р?Р?
Что-то пошло не так и вместо русского текста отобразились какие-то закорючки. В чем тут причина и как эту ситуацию можно исправить?
Первое, что приходит в голову — это какая-то проблема с кодировкой. Нужно разобраться, в какой момент она возникает.
Посмотрим, какая кодировка у файла с нашим кодом. Она отображается в правом нижнем углу окна. Видим, что это UTF-8.
А какая кодировка используется в OC? Определить это можно разными путями. Например, в Windows в реестре (для его запуска из консоли используйте команду regedit) по следующему адресу HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage можно посмотреть, чему равен параметр под названием ACP (ANSI code page).
У меня он имеет значение 1251. Это стандартная кодировка для русских версий Windows. У вас она может быть другой — все зависит от языка (кодировки) системы.
Есть еще один способ узнать кодировку, который подходит для любой ОС — это воспользоваться Java, поместив в наш класс две новых строки:
import java.nio.charset.Charset; public class MyFirstApp < public static void main(String[] args) < System.out.println(Charset.defaultCharset().displayName()); >>
Вникать в то, что означает этот код мы не будем — наша задача лишь получить универсальным способом значение кодировки. Но если в двух словах, то в первой строке мы подключаем класс Charset, а в пятой выводим на экран значение кодировки по умолчанию для ОС, воспользовавшись возможностями данного класса.
> java MyFirstApp.java windows-1251
> chcp Текущая кодовая страница: 866
Она выдала 866. Это же число можно наблюдать и в реестре у параметра OEMCP (Original equipment manufacturer code page). Это DOS-кодировка, которая досталась нам в наследство.
Не удивительно, что Java не смогла корректно отобразить кириллический текст, когда используется столько кодировок в одной ОС.
Дело в том, что во время компиляции компилятор определяет кодировку исходного кода, опираясь на значение кодировки по умолчанию (на кодировку вашей ОС), а не на кодировку файла. В разных системах используются разные кодировки по умолчанию: в Windows — windows-1251, в Linux и Mac — UTF-8.
В Windows, ко всему прочему, еще и кодировка консоли не совпадает с кодировкой системы! Консоль по историческим причинам имеет кодировку Cp866 (видимо, в целях совместимости).
Затем JVM определяет кодировку по умолчанию во время запуска, используя системное свойство file.encoding. Значение для этого свойства устанавливается Java-машиной один раз при старте на основании данных, взятых из ОС.
Кодировка, используемая при выводе в консоль тоже системная, а не консоли. Если они не совпадают, то не видать нам корректного вывода кириллицы.
В итоге кодировку надо учитывать во время компиляции, во время запуска и во время вывода текста на консоль, например, при вводе с клавиатуры. Невыполнение этого правила в любом из этих мест способно вызвать проблемы.
У компилятора есть опция -encoding. Она позволяет принудительно указать кодировку исходника, чтобы компилятор не определял ее, опираясь на кодировку системы. Как вы помните, файл имеет кодировку UTF-8. Вот ее мы и укажем: javac -encoding utf8 MyFirstApp.java.
Если для однофайловых программы вы решили не использовать явно компиляцию, а сразу писать java, то для переопределения системного свойства file.encoding существует специальная команда. Вместе с ней запуск программы будет выглядеть так:
> java -Dfile.encoding=UTF8 MyFirstApp.java Написано однажды, работает везде
-Dfile.encoding — это уже параметр JVM, с помощью которого мы устанавливаем принудительно нужное нам значение, сохраняя его в file.encoding.
От всех этих параметров и правил может закружиться голова. И все эти годы Java-разработчики жили и мучились с кодировками и ошибками, которые они вызывали в программах.
Но не прошло и 25 лет, как появилась хорошая новость. Начиная с Java 18, значение кодировки по умолчанию определяется не исходя из кодировки ОС, а исходя из того, что она является UTF-8 — отныне это кодировка по умолчанию. Никакие параметры больше не нужны. Убедимся в этом:
> "C:\Program Files\java\SapMachine-18\bin\java.exe" MyFirstApp.java Написано однажды, работает везде > "C:\Program Files\java\SapMachine-18\bin\javac.exe" MyFirstApp.java > "C:\Program Files\java\SapMachine-18\bin\java.exe" MyFirstApp Написано однажды, работает везде
Для тех, кто использует Java меньше 18, придется использовать все параметры, указанные ранее — ни куда от них не деться. Но и тут есть выход.
Утомительно все время указывать кодировку. Этот вопрос можно решить, используя JAVA_TOOL_OPTIONS со значением -Dfile.encoding=UTF8. Необходимо установить эту переменную и ее значение в качестве переменной среды (ранее мы уже имели дело с другой переменной — JAVA_HOME). Не забудьте перезапустить консоль.
Благодаря этой переменной кодировка будет устанавливаться автоматически при каждом запуске JVM. При этом в консоли будет отображаться сообщение:
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8
> java MyFirstApp.java Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 Написано однажды, работает везде > javac MyFirstApp.java Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 > java MyFirstApp Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 Написано однажды, работает везде
Забежим немного вперед и рассмотрим ситуацию, когда нам нужно ввести с клавиатуры что-то на русском языке. Да, ввод мы еще не проходили, но мне совсем не хочется размазывать эту информацию по разным статьям. Пусть все, что связано с компиляцией и запуском, с кодировками, их проблемы и решения будет в одном месте.
Добавим в наш код библиотечный класс Scanner, который позволит считывать, введенные с клавиатуры символы, и пару строк с его использованием:
import java.util.Scanner; public class MyFirstApp < public static void main(String[] args) < System.out.println("Написано однажды, работает везде"); Scanner console = new Scanner(System.in); System.out.print("Введите свое имя: "); System.out.println(console.nextLine()); >>
> java MyFirstApp.java Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 Написано однажды, работает везде Введите свое имя: Максим .
замените Scanner console = new Scanner(System.in); на Scanner console = new Scanner(System.in, "cp866");
> java MyFirstApp.java Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 Написано однажды, работает везде Введите свое имя: МАКСИМ ШШШШ ИИИИ МАКСИМ ШШШШ ИИИИ
Если после всего проделанного, что я описал выше, у вас вместо русского текста выводится пустая строка, то необходимо в консоли ввести chcp 866 и заново запустить программу.
В этой статье мы научились компилировать и запускать java-программы, нашли способ побороть проблему с выводом кириллических символов в консоль, а также лучше стали понимать, как работают все эти механизмы.
Если не использовать в коде программы или во время ввода с клавиатуры русский язык, то никаких проблем с кодировками не возникнет. Но, если вы все же ступили на путь использования кириллических символов, то вам придется проделать все те настройки, которые были описаны во второй половине статьи. Таких проблем в профессиональных средах разработки практически не бывает, т. к. они умеют правильно отображать текст в консоли, делая автоматически, если нужно, его перекодировку.
UTF8 filenames with Java
Functions from java.io solely depend on the locale settings of your operating system. If you want to create the directory /tmp/foö/bär, save this snippet to a file called javaapplication1/JavaApplication1.java:
package javaapplication1; public class JavaApplication1 < static < System.setProperty("file.encoding", "UTF-8"); System.setProperty("sun.jnu.encoding", "UTF-8"); >public static void main(String[] args) < new java.io.File("/tmp/foö/bär").mkdirs(); >>
$ javac -encoding UTF-8 javaapplication/JavaApplication1.java
$ java -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 javaapplication.JavaApplication1
If your environment is set to ASCII or not set at all, you will get /tmp/fo?/b?r. If your environment is set to ISO-8859-1 instead, you will get /tmp/fo\366/b\344r. System properties given on the commandline and in the static initialization block have no effect. You have to set your environment to an *.utf-8 locale:
$ LC_ALL=de_DE.utf-8 java javaapplication.JavaApplication1
Now you have a directory tree named /tmp/f\303\266/b\303\244r, which is the correct byte level encoding for UTF-8.
Using java.nio.*
In contrast to java.io, java.nio honors the sun.jnu.encoding system property set in the static initialization block:
package javaapplication1; public class JavaApplication1 < static < System.setProperty("sun.jnu.encoding", "UTF-8"); >public static void main(String[] args) throws java.io.IOException < java.nio.file.Files.createDirectories(java.nio.file.Paths.get("/tmp/foö/bär")); >>
$ javac -encoding UTF-8 javaapplication/JavaApplication1.java
But, you can omit any locale settings and system properties on the command line, and will still get directories with UTF-8 names:
$ java javaapplication.JavaApplication1
package javaapplication1; public class JavaApplication1 < public static void main(String[] args) throws java.io.IOException < java.nio.file.Files.createDirectories(java.nio.file.Paths.get("/tmp/foö/bär")); >>
$ LC_ALL=de_DE.utf-8 java javaapplication.JavaApplication1
Btw, java.nio just defines static functions plus several interfaces. For an object oriented language I find this highly unclean.