Кодирование символов в Java

Открываю файл блокнотом, пишу там: «ą» сохранить и закрыть.

Я пытаюсь прочитать этот файл двумя способами

Первый:

        InputStream inputStream = Files.newInputStream(Paths.get("file.txt"));
        int result = inputStream.read();
        System.out.println(result);
        System.out.println((char) result);

196 Ä

Второй:

        InputStream inputStream = Files.newInputStream(Paths.get("file.txt"));
        Reader reader = new InputStreamReader(inputStream);
        int result = reader.read();
        System.out.println(result);
        System.out.println((char) result);

261 ą

Вопросов: 1) В двоичном режиме эта буква сохраняется как 196? Почему не как 261? 2) Это письмо сохраняется как 196 в какой кодировке?

Я пытаюсь понять, почему есть различия

В какой кодировке вы сохранили свой односимвольный файл в Блокноте?

Tim Biegeleisen 03.08.2018 13:02

Блокнот ++ показывает «utf-8 без бомбы», но я не вижу в таблице utf-8 буквы «ą» с кодом: 196

Thomas Banderas 03.08.2018 13:06
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
2
82
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы получаете значение decimal букв LATIN Вам необходимо savefile со стандартом кодирования UTF-8.

Убедитесь, что читаете их с похожими стандартами.

0x0105 261 LATIN SMALL LETTER A WITH OGONEK ą

0x00C4 196 LATIN CAPITAL LETTER A WITH DIAERESIS �

Обратитесь к этому: -https://www.ssec.wisc.edu/~tomw/java/unicode.html

ОП уже знает об этом. Итак, он не это имел в виду.

snr 03.08.2018 13:12

Попробуйте использовать InputStreamReader с кодировкой UTF-8, которая соответствует кодировке, используемой для записи файла из Notepad ++:

// this will use UTF-8 encoding by default
BufferedReader in = Files.newBufferedReader(Paths.get("file.txt"));

String str;
if ((str = in.readLine()) != null) {
    System.out.println(str);
}
in.close();

У меня нет точного / воспроизводимого ответа на вопрос, почему вы видите результат, который видите, но если вы читаете с неправильной кодировкой, вы не обязательно увидите то, что сохранили. Например, если одиночный символ ą был закодирован двумя байтами, но вы читали его как ASCII, тогда вы могли бы получить обратно два символа, которые не соответствовали бы вашему исходному файлу.

Символ ą кодируется до двух байтов (C4 85), при этом C4 представляет собой 196 в десятичной системе счисления. Так что они действительно читают только первый байт.

TiiJ7 03.08.2018 13:15

@ TiiJ7 Спасибо за детективную работу. Я бы не знал, как это понять, но проблема с кодировкой - это первое, что бросилось мне в глаза :-)

Tim Biegeleisen 03.08.2018 13:16

Поскольку вы читаете эти две буквы в разных кодировках, вы можете проверить свою кодировку через InputStreamReader::getEncoding.

String s = "ą";

char iso_8859_1 = new String(s.getBytes(), "iso-8859-1").charAt(0);
char utf_8 = new String(s.getBytes(), "utf-8").charAt(0);   

System.out.println((int) iso_8859_1 + " " + iso_8859_1);
System.out.println((int) utf_8 + " " + utf_8);

На выходе

196 Ä
261 ą
Ответ принят как подходящий

UTF-8 кодирует значения из диапазона U+0080 - U+07FF как два байта в форме 110xxxxx10xxxxxx (подробнее см. В вики). Таким образом, для значения доступны только 11 байт xxxxx xxxxxx.

ą - это индексируется как U + 0105, где 0105 - шестнадцатеричное значение (как десятичное - 261). В двоичном виде это можно представить как

      01       05    (hex)
00000001 00000101    (bin)
     xxx xxxxxxxx <- values for U+0080 - U+07FF range encode only those bits

     001 00000101 <- which means `x` will be replaced by only this part 

Таким образом, кодировка UTF-8 добавит маску 110xxxxx10xxxxxx, что означает, что она объединит

110xxxxx 10xxxxxx
   00100   000101

в (два байта):

11000100 10000101

Теперь InputStream читает данные как сырые байты. Итак, когда вы звоните в inputStream.read(); в первый раз, вы получаете 11000100, который в десятичном виде является 196. Повторный вызов inputStream.read(); вернет 10000101, который является 133 в десятичном виде.

Reader были введены в Java 1.1, поэтому мы могли избежать такого беспорядка в нашем коде. Вместо этого мы можем указать, какую кодировку должен использовать Reader (или позволить ему использовать значение по умолчанию), чтобы получить правильно закодированные значения, как в этом случае 00000001 00000101 (без маски), который равен 0105 в шестнадцатеричной форме и 261 в десятичной форме.


Коротко

  • используйте Reader (с правильно заданной кодировкой), если вы хотите читать данные как текст,
  • используйте Streams, если вы хотите читать данные как необработанные байты.

Другие вопросы по теме