1. UTF-8 — король современных кодировок
В работе программиста вы столкнётесь с файлами, сетевыми протоколами, базами данных, веб‑сервисами, где используются разные кодировки. Если вы не знаете, чем UTF-8 отличается от ISO-8859-1, то легко попадёте в ловушку: ваш код будет работать «у меня на компьютере», но сломается у коллеги из Франции или в облаке, где другая система по умолчанию.
Сегодня мы разберём три основные кодировки, которые встречаются в реальной жизни Java‑разработчика:
- UTF-8 — современный универсальный стандарт.
- UTF-16 — внутренний формат строк в Java и часто встречается в Windows.
- ISO-8859-1 (Latin-1) — старая, но до сих пор встречающаяся однобайтовая кодировка для западноевропейских языков.
Давайте познакомимся с ними поближе!
Что это такое?
UTF-8 (Unicode Transformation Format, 8-bit) — это кодировка, которая умеет представлять любой символ из огромной таблицы Unicode (а это и кириллица, и китайские иероглифы, и эмодзи, и даже редкие древние письменности). Причём делает это очень хитро: для самых популярных символов (английские буквы, цифры, знаки препинания) используется всего 1 байт. Для остальных — 2, 3 или даже 4 байта.
Факт: Все современные сайты, большинство JSON- и XML-файлов, и даже исходники на Java по умолчанию используют UTF-8.
Сколько нужно байт для одного символа?
- ASCII‑символы (английские буквы, цифры, знаки препинания) — 1 байт.
- Кириллические символы, европейские символы с диакритикой — 2 байта.
- Китайские, японские, корейские иероглифы — 3 байта.
- Супер‑экзотика, эмодзи — 4 байта.
Пример:
| Символ | Код в Unicode | UTF-8 (в байтах) |
|---|---|---|
| A | |
|
| Я | |
|
| € | |
|
| 😀 | |
|
Почему UTF-8 — это круто?
- Обратно совместим с ASCII: если файл содержит только английские символы, он выглядит абсолютно так же, как в ASCII.
- Компактность: для коротких текстов на английском языке файл в UTF-8 будет минимального размера.
- Международность: поддерживает все языки мира, включая эмодзи.
- Стандарт для web: HTML, CSS, JSON, XML, JavaScript — всё это по умолчанию UTF-8.
Пример использования в Java
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class Utf8Demo {
public static void main(String[] args) throws Exception {
// Запись строки в файл в UTF-8
String text = "Привет, мир! 😀";
Files.write(Paths.get("utf8.txt"), text.getBytes(StandardCharsets.UTF_8));
// Чтение файла в UTF-8
String read = Files.readString(Paths.get("utf8.txt"), StandardCharsets.UTF_8);
System.out.println(read); // Выведет: Привет, мир! 😀
}
}
Здесь мы сначала явно кодируем строку в UTF-8 перед записью, а потом так же явно указываем кодировку при чтении. Благодаря этому текст сохраняется и восстанавливается без искажений, даже если в нём есть эмодзи или символы разных языков.
Когда использовать UTF-8?
Всегда, если только у вас нет очень веской причины выбрать что-то другое. Это универсальный выбор для всех современных приложений.
2. UTF-16 — внутренний формат Java
UTF-16 — это ещё одна кодировка из семейства Unicode. В отличие от UTF-8, она чаще всего использует 2 байта на символ. Именно так Java хранит строки (String) и символы (char) внутри JVM. Но есть нюанс: некоторые символы (например, редкие иероглифы или эмодзи) требуют уже 4 байта (две «суррогатные пары»).
Где используется UTF-16?
- Внутри Java: все строки и символы — это UTF-16.
- Windows: многие системные файлы в Windows используют UTF-16 (например, Notepad по умолчанию сохраняет в UTF-16 LE).
- Некоторые сетевые протоколы и форматы: например, XML может быть в UTF-16.
Пример: как выглядит строка в UTF-16
| Символ | Код в Unicode | UTF-16 (байты) | Комментарий |
|---|---|---|---|
| A | |
|
2 байта |
| Я | |
|
2 байта |
| € | |
|
2 байта |
| 😀 | |
|
4 байта (суррогатная пара) |
Пример использования в Java
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class Utf16Demo {
public static void main(String[] args) throws Exception {
String text = "Привет, мир! 😀";
// Запись строки в файл с использованием кодировки UTF-16
Files.write(Paths.get("utf16.txt"), text.getBytes(StandardCharsets.UTF_16));
// Чтение содержимого файла с указанием кодировки UTF-16
String read = Files.readString(Paths.get("utf16.txt"), StandardCharsets.UTF_16);
System.out.println(read); // Корректный вывод исходной строки
}
}
Ключевой момент: при записи и чтении нужно указывать одну и ту же кодировку. Если записать в UTF-16, а прочитать по умолчанию (например, в UTF-8), возникнет «каша» из символов.
Особенности UTF-16
- Фиксированная длина для большинства символов: 2 байта.
- Суррогатные пары: часть символов (например, эмодзи) требуют 4 байта.
- Byte Order Mark (BOM): в начале файла часто добавляется специальная «шапка», указывающая порядок байтов (LE — little endian, BE — big endian). Java обычно сама понимает BOM.
- Не совместим с ASCII: если вы откроете UTF-16 файл в редакторе, который ждёт ASCII или UTF-8, увидите кучу нулей и странных знаков.
Когда использовать UTF-16?
- Если вы работаете с очень специфичными системами или файлами, которые требуют именно UTF-16 (например, интеграция с Windows‑программами).
- Для обычных приложений и файлов — лучше использовать UTF-8.
4. ISO-8859-1 (Latin-1) — привет из прошлого
ISO-8859-1, или Latin-1 — это однобайтовая кодировка, созданная ещё в далёкие 80‑е годы прошлого века. Она поддерживает 256 символов: английский алфавит, западноевропейские буквы с диакритикой (é, ü, ç и т.д.), знаки препинания, спецсимволы.
Факт: В этой кодировке нет кириллицы, греческих, арабских, китайских и других не‑латинских символов.
Где встречается ISO-8859-1?
- В старых файлах и программах (особенно в Европе).
- В некоторых базах данных и протоколах «по умолчанию».
- В HTTP‑заголовках (по стандарту текстовые данные без указания кодировки считаются ISO-8859-1, хотя на практике это редко).
Пример: как выглядит строка в ISO-8859-1
| Символ | Код в Unicode | ISO-8859-1 (байт) | Комментарий |
|---|---|---|---|
| A | |
|
То же, что в ASCII |
| é | |
|
Французский |
| ü | |
|
Немецкий |
| Я | |
— | Нет такого символа! |
Пример использования в Java
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.charset.Charset;
public class Latin1Demo {
public static void main(String[] args) throws Exception {
String text = "Bonjour, ça va? Café!"; // Французский текст
Files.write(Paths.get("latin1.txt"), text.getBytes(StandardCharsets.ISO_8859_1));
// Чтение файла в ISO-8859-1
String read = Files.readString(Paths.get("latin1.txt"), StandardCharsets.ISO_8859_1);
System.out.println(read); // Всё читается корректно!
// Попробуем записать кириллицу
String cyrillic = "Привет, мир!";
try {
Files.write(Paths.get("badlatin1.txt"), cyrillic.getBytes(StandardCharsets.ISO_8859_1));
} catch (Exception e) {
System.out.println("Ошибка: невозможно записать кириллицу в ISO-8859-1!");
}
}
}
Кодировка ISO-8859-1 (Latin-1) поддерживает только западноевропейские символы — например, буквы с акцентами из французского, немецкого или испанского языков. Поэтому французский текст записывается и читается без проблем.
Однако кириллица в этой кодировке отсутствует, и при попытке её записать возникает ошибка или потеря символов. Именно поэтому для многоязычных текстов лучше использовать UTF-8 или UTF-16, которые охватывают гораздо больший набор символов.
Особенности ISO-8859-1
- Ограничение по языкам: только западноевропейские языки.
- 1 байт на символ: компактно, но очень ограниченно.
- Запись «чужих» символов приводит к искажению: если попытаться сохранить кириллицу или иероглифы, они превратятся в вопросительные знаки или мусор.
Когда использовать ISO-8859-1?
- Только если вы интегрируетесь с очень старой системой или базой данных, которая требует именно эту кодировку.
- Для современных задач — не рекомендуется.
5. Полезные нюансы
Таблица сравнения кодировок
| Кодировка | Байт на символ | Какие языки | Совместимость с ASCII | Где используется |
|---|---|---|---|---|
|
1-4 | Все | Полная | Web, Java, JSON, XML, Linux |
|
2 или 4 | Все | Нет | Внутри Java, Windows, XML |
|
1 | Западная Европа | Полная | Старые программы, базы, HTTP |
Как выбрать кодировку?
- UTF-8 — ваш выбор для 99% задач. Совместим с ASCII, поддерживает все языки, минимальный размер для английского текста, стандарт для web и Java.
- UTF-16 — используйте только если того требует спецификация или интеграция с Windows‑программами.
- ISO-8859-1 — только для обратной совместимости со старыми системами. Никогда не используйте для хранения кириллицы, греческого, арабского и т.д.
Как узнать, в какой кодировке файл?
- Откройте файл в редакторе, который показывает кодировку (например, Notepad++, VS Code).
- Если видите «кракозябры», попробуйте открыть файл в другой кодировке.
- В терминале Linux можно использовать команду file имя_файла — она иногда подсказывает кодировку.
Как задать кодировку в Java?
Используйте классы из пакета java.nio.charset:
- StandardCharsets.UTF_8
- StandardCharsets.UTF_16
- StandardCharsets.ISO_8859_1
Или через имя кодировки:
Charset windows1251 = Charset.forName("Windows-1251");
Charset utf8 = Charset.forName("UTF-8");
6. Практика: что будет, если перепутать кодировку?
Давайте попробуем небольшой эксперимент. Запишем строку в UTF-8, а потом попробуем прочитать её как ISO-8859-1:
import java.nio.file.*;
import java.nio.charset.*;
public class EncodingMismatchDemo {
public static void main(String[] args) throws Exception {
String text = "Привет, мир!"; // Кириллица
// Записываем как UTF-8
Files.write(Paths.get("utf8demo.txt"), text.getBytes(StandardCharsets.UTF_8));
// Читаем как ISO-8859-1 (НЕПРАВИЛЬНО!)
String wrong = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.ISO_8859_1);
System.out.println("Неправильное чтение: " + wrong);
// Теперь читаем правильно
String correct = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.UTF_8);
System.out.println("Правильное чтение: " + correct);
}
}
Результат:
- В первом случае вы увидите «кракозябры» — набор странных символов.
- Во втором случае — нормальный русский текст.
7. Типичные ошибки при работе с кодировками
Ошибка №1: Чтение файла в неверной кодировке. Если файл записан в UTF-8, а вы читаете его как ISO-8859-1 или Windows-1251, то все не‑ASCII символы превратятся в мусор. Это классика жанра: «у меня на компьютере всё работает, а у коллеги — нет».
Ошибка №2: Попытка записать «чужие» символы в однобайтовую кодировку. Если вы попытаетесь сохранить кириллицу или иероглифы в ISO-8859-1, Java заменит их на вопросительные знаки или выдаст ошибку. Данные будут потеряны безвозвратно.
Ошибка №3: Использование системной кодировки «по умолчанию». В некоторых методах Java (например, new FileReader("file.txt")) кодировка не указывается явно — используется системная. На одном компьютере это может быть UTF-8, на другом — Windows-1251, на третьем — вообще что-то экзотическое. Поэтому всегда используйте методы, где можно явно указать кодировку.
Ошибка №4: Открытие UTF-16 файла как UTF-8. Файл, записанный в UTF-16, если открыть как UTF-8, будет содержать кучу нулей и странных символов, потому что байты будут интерпретироваться неверно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ