От компиляции до времени выполнения, как действительно работает кодировка Java String - PullRequest
18 голосов
/ 29 января 2010

Недавно я понял, что не до конца понимаю процесс кодирования строк 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.

Теперь я скомпилирую его с различными кодировками файлов, 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] кодовыми страницами windows-1252.

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

  1. Ожидается ли, что кодовая страница / кодировка компилируемого файла Java будет идентична кодировке по умолчанию системы, в которой он компилируется?Всегда ли они синонимичны?
  2. Скомпилированное представление не зависит от кодировки времени компиляции, так ли это на самом деле?
  3. Означает ли это, что строки в файлах Java могут интерпретироваться?иначе во время выполнения, если они не используют стандартные символы для текущей кодировки / локали?
  4. Что еще я действительно должен знать о кодировании строк и символов в Java?

Ответы [ 4 ]

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

Примечания:

14 голосов
/ 30 января 2010

Краткое изложение «что нужно знать» о кодировках строк в Java:

  • Экземпляр String в памяти - это последовательность 16-битных «кодовых единиц», которые Java обрабатывает как значения char. Концептуально эти кодовые единицы кодируют последовательность «кодовых точек», где кодовая точка - это «число, присваиваемое данному символу в соответствии со стандартом Unicode». Кодовые точки варьируются от 0 до чуть более миллиона, хотя до сих пор было определено только около 100 тысяч. Кодовые точки от 0 до 65535 кодируются в одну кодовую единицу, тогда как другие кодовые точки используют две кодовые единицы. Этот процесс называется UTF-16 (он же UCS-2). Есть несколько тонкостей (некоторые кодовые точки недействительны, например, 65535, и в первых 65536 есть диапазон из 2048 кодовых точек, зарезервированных именно для кодирования других кодовых точек).
  • Кодовые страницы и т.п. не влияют на то, как Java хранит строки в ОЗУ. Вот почему «Unicode» начинается с «Uni». Пока вы не выполняете ввод-вывод со своими строками, вы находитесь в мире Unicode, где все используют одинаковое отображение символов для кодирования точек.
  • Кодировки включаются при кодировании строк в байтах или декодировании строк из байтов. Если явно не указано, Java будет использовать кодировку по умолчанию, которая зависит от "локали" пользователя - нечеткого совокупного понятия о том, что заставляет компьютер в Японии говорить по-японски. Когда вы печатаете строку с 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 голосов
/ 29 января 2010

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

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

1 голос
/ 29 января 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...