Подтвердить что ты не робот

От компиляции до времени выполнения, как действительно работает кодировка Java String

Недавно я понял, что я не полностью понимаю процесс кодирования строки Java.

Рассмотрим следующий код:

public class Main
{
    public static void main(String[] args)
    {
        System.out.println(java.nio.charset.Charset.defaultCharset().name());
        System.out.println("ack char: ^"); /* where ^ = 0x06, the ack char */
    }
}

Поскольку управляющие символы интерпретируются по-разному между окнами-1252 и ISO-8859-1, я выбрал ack char для тестирование.

Теперь я скомпилирую его с различными кодировками файлов, UTF-8, windows-1252 и ISO-8859-1. Оба скомпилируются в одну и ту же вещь, байт за байт, как проверено md5sum.

Затем я запускаю программу:

$ java Main | hexdump -C
00000000  55 54 46 2d 38 0a 61 63  6b 20 63 68 61 72 3a 20  |UTF-8.ack char: |
00000010  06 0a                                             |..|
00000012

$ java -Dfile.encoding=iso-8859-1 Main | hexdump -C
00000000  49 53 4f 2d 38 38 35 39  2d 31 0a 61 63 6b 20 63  |ISO-8859-1.ack c|
00000010  68 61 72 3a 20 06 0a                              |har: ..|
00000017

$ java -Dfile.encoding=windows-1252 Main | hexdump -C
00000000  77 69 6e 64 6f 77 73 2d  31 32 35 32 0a 61 63 6b  |windows-1252.ack|
00000010  20 63 68 61 72 3a 20 06  0a                       | char: ..|
00000019

Он правильно выводит 0x06 независимо от того, какая кодировка используется.

Хорошо, он по-прежнему выводит те же 0x06, которые будут интерпретироваться как печатные [ACK] char кодами-окнами windows-1252.

Это приводит меня к нескольким вопросам:

  • Является ли кодовая страница/кодировка скомпилированного файла Java ожидаемой идентичной кодировке по умолчанию системы, в которой она компилируется? Являются ли эти два синонимами?
  • Скомпилированное представление, похоже, не зависит от кодировки времени компиляции, действительно ли это так?
  • Означает ли это, что строки в файлах Java могут интерпретироваться по-разному во время выполнения, если они не используют стандартные символы для текущей кодировки/локали?
  • Что еще я должен знать о кодировке строк и символов в Java?
4b9b3361

Ответ 1

  • Исходные файлы могут быть в любой кодировке
  • Вам нужно указать компилятору кодировку исходных файлов (например, javac -encoding...); в противном случае предполагается кодирование платформы.
  • В двоичных файлах файлов классов строковые литералы хранятся как (измененные) UTF-8, но если вы не работаете с байт-кодом, это не имеет значения (см. JVM спецификации)
  • Строки в Java - это UTF-16, всегда (см. Спецификация языка Java)
  • System.out PrintStream преобразует ваши строки из UTF-16 в байты в системном кодировании до их записи в stdout

Примечания:

Ответ 2

Резюме "что знать" о строковых кодировках в Java:

  • A String экземпляр в памяти представляет собой последовательность из 16-разрядных "блоков кода", которые Java обрабатывает как значения char. Концептуально эти кодовые единицы кодируют последовательность "кодовых точек", где кодовая точка - это "номер, приписываемый заданному символу в соответствии со стандартом Unicode". Кодовые точки варьируются от 0 до бит более одного миллиона, хотя до сих пор было определено только 100 тысяч или около того. Кодовые точки от 0 до 65535 кодируются в единый блок кода, в то время как другие кодовые точки используют два блока кода. Этот процесс называется UTF-16 (он же UCS-2). Есть несколько тонкостей (некоторые кодовые точки недействительны, например 65535, и в первом 65536 содержится всего 2048 кодовых точек, зарезервированных именно для кодирования других кодовых точек).
  • Кодовые страницы и тому подобное не влияют на то, как Java хранит строки в ОЗУ. Поэтому "Unicode" начинается с "Uni". Пока вы не выполняете ввод-вывод со своими строками, вы находитесь в мире Unicode, где все используют одинаковое сопоставление символов с кодовыми точками.
  • Шрифты вступают в действие при кодировании строк в байтах или декодировании строк из байтов. Если явно не указано, Java будет использовать кодировку по умолчанию, которая зависит от пользователя "locale", нечеткое совокупное представление о том, что делает компьютер в Японии японским. Когда вы печатаете строку с помощью System.out.println(), JVM преобразует строку в нечто подходящее для любых символов, что часто означает преобразование их в байты, используя кодировку, которая зависит от текущей локали (или то, что JVM догадывается о текущий язык).
  • Одно Java-приложение - это компилятор Java. Компилятору Java необходимо интерпретировать содержимое исходных файлов, которые на системном уровне представляют собой только пучок байтов. Компилятор Java затем выбирает кодировку по умолчанию для этого, и он делает это в зависимости от текущей локали, как это делает Java, потому что компилятор Java сам написан на Java. Компилятор Java (javac) принимает флаг командной строки (-encoding), который может использоваться для переопределения этого выбора по умолчанию.
  • Компилятор Java создает файлы классов, которые не зависят от языка. Строковые литералы попадают в эти файлы классов с кодировкой (вроде) UTF-8, независимо от кодировки, которую компилятор Java использовал для интерпретации исходных файлов. Локаль в системе, на которой работает Java-компилятор, влияет на интерпретацию исходного кода, но как только компилятор Java понял, что ваша строка содержит кодовую точку 6, то эта кодовая точка - это то, что проделает путь к файлам классов, и никто другой. Обратите внимание, что коды с 0 по 127 имеют одинаковую кодировку в UTF-8, CP-1252 и ISO-8859-1, поэтому вы не удивитесь.
  • Даже при этом экземпляры String не зависят от какого-либо кодирования, если они остаются в ОЗУ, некоторые из операций, которые вы, возможно, захотите выполнить в строках, зависят от языка. Это не вопрос кодирования; но локаль также определяет "язык", и бывает так, что понятия верхнего и нижнего регистров зависят от используемого языка. Обычный Подозреваемый вызывает "unicode".toUpperCase(): это дает "UNICODE", за исключением того, что текущая локаль является турецкой, и в этом случае вы получаете "UNİCODE" ( "I" имеет точку). Основное предположение здесь состоит в том, что если текущий язык является турецким, то данные, которыми управляет приложение, вероятно, являются турецким текстом; лично я нахожу это предположение в лучшем случае сомнительным. Но так оно и есть.

В практическом плане вы должны явно указывать кодировки в своем коде, по крайней мере, большую часть времени. Не вызывайте String.getBytes(), звоните String.getBytes("UTF-8"). Использование кодировки по умолчанию, зависящей от локали, отлично, когда она применяется к некоторым данным, обмениваемым с пользователем, таким как файл конфигурации или сообщение для немедленного отображения; но в других местах, избегайте локально зависимых методов, когда это возможно.

Среди других языковых зависимостей Java есть календари. Существует целый часовой бизнес, который зависит от "часового пояса", который должен относиться к географическому положению компьютера (и это не является частью "локали" stricto sensu...). Кроме того, бесчисленные приложения Java таинственно терпят неудачу при запуске в Бангкоке, потому что в тайском языке Java по умолчанию использует буддийский календарь, согласно которому текущий год равен 2553.

Как правило, предположим, что мир огромен (он есть!) и сохраняет вещи родовыми (не делайте ничего, что зависит от набора символов до самого последнего момента, когда фактически должен выполняться ввод-вывод).

Ответ 3

При компиляции с различными кодировками эти кодировки влияют только на исходные файлы. Если у вас нет специальных символов внутри ваших источников, не будет никакой разницы в полученном байтовом коде.

Для времени выполнения используется кодировка по умолчанию для операционной системы. Это не зависит от кодировки, которую вы использовали для компиляции.

Ответ 4

Erm на основе this и this Управляющий символ ACK в обоих кодировках точно такой же. Разница, о которой вы указали, говорит о том, как у DOS/Windows на самом деле есть символы для большинства управляющих символов в Windows-1252 (например, символы Heart/Club/Spade/Diamond и simileys), а ISO-8859 - нет.