1. Симптоми помилок
В ідеальному світі програмісти завжди знають, у якому кодуванні збережено файл, і правильно зазначають його під час читання. Але реальність — це світ, де файли мандрують між Windows, Linux, серверами, редакторами, і кожен по‑своєму трактує байти. У результаті ми стикаємося з такими симптомами:
- «Кракозябри» — замість очікуваного тексту бачимо дивні символи, знаки питання, квадратики або набір літер, який не схожий на жодну мову світу.
- Втрата символів — частина тексту зникає або замінюється на ?.
- Винятки — наприклад, MalformedInputException, коли Java не може «перетравити» байти у вибраному кодуванні.
- Помилки під час парсингу — програма не може правильно обробити файл, тому що ключові слова або структури пошкоджені через спотворення тексту.
Ось класичний приклад «кракозябр» під час читання кириличного файлу в неправильному кодуванні:
Очікували: Привіт, світе!
Отримали: Привет, мир
Це не нова мова, а результат того, що байти були інтерпретовані не тим «словником», який потрібен.
2. Чому виникають помилки: корінь зла
Файл збережено в одному кодуванні, а читається в іншому
Припустімо, хтось зберіг файл у Windows-1251, а ви відкриваєте його в UTF-8. Java чесно намагається декодувати байти за правилами UTF-8, та виходить нісенітниця, тому що значення байтів не збігаються з очікуваними.
Використання системного кодування «за замовчуванням»
Якщо ви не зазначаєте кодування явно, Java використовує системне — те, що встановлене на вашому комп’ютері. У Windows із російською локаллю це може бути Windows-1251, у Linux — UTF-8, на Mac — теж UTF-8. Файл, який чудово відкривається у вас, може стати непридатним для читання у колеги з іншою ОС.
Використання застарілих конструкторів
У старих версіях Java (і в деяких підручниках) часто трапляються такі конструкції з FileReader/FileWriter, які використовують системне кодування і не дають вам контролю — це пастка та джерело «кракозябр».
FileReader reader = new FileReader("file.txt");
FileWriter writer = new FileWriter("file.txt");
Наявність або відсутність BOM (Byte Order Mark)
Деякі кодування (наприклад, UTF-8 із BOM або UTF-16) додають на початок файлу спеціальні байти, щоб сигналізувати про кодування. Якщо програма не очікує BOM або навпаки — чекає його, але не знаходить, можуть виникнути проблеми: або перші символи файлу будуть спотворені, або файл не розпізнається взагалі.
3. Як проявляються помилки: розбір на практиці
Приклад 1: Файл із кирилицею, збережений у Windows-1251, читається як UTF-8
import java.nio.file.*;
import java.nio.charset.*;
public class EncodingDemo {
public static void main(String[] args) throws Exception {
Path path = Paths.get("russian.txt");
// Файл збережено у Windows-1251, читаємо як UTF-8 — будуть кракозябри!
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
System.out.println(reader.readLine());
}
}
}
У результаті замість «Привіт, світе!» ви побачите набір дивних символів.
Приклад 2: Файл збережено в UTF-8, читається як ISO-8859-1
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.ISO_8859_1)) {
System.out.println(reader.readLine());
}
Результат: Усі не-ASCII-символи будуть перетворені на сміття або замінені на ?.
Приклад 3: Виняток під час читання файлу
Якщо байти не відповідають правилам обраного кодування, Java може кинути виняток:
Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
...
Це означає, що Java зустріла байт, який не може бути коректно інтерпретований у вибраному кодуванні.
4. Діагностика: як зрозуміти, що не так із кодуванням
Перевіряйте кодування файлу
- У редакторах на кшталт Notepad++, VS Code або Sublime Text зазвичай можна переглянути або змінити кодування файлу (зазвичай на нижній панелі).
- У Linux командою можна отримати підказку про кодування (але не завжди на 100 % точно):
file имя_файла.txt
Перевіряйте системне кодування Java
Виведіть у консоль значення властивості file.encoding:
System.out.println(System.getProperty("file.encoding"));
Використовуйте тестові дані
Створіть невеликий файл із різними символами (кириличні, латинські, спецсимволи, емодзі), спробуйте прочитати його з різними кодуваннями і подивіться, коли результат збігається з очікуваннями.
Завжди явно вказуйте кодування
Щойно ви бачите в коді читання/запис файлу без зазначення кодування — це привід насторожитися. Наприклад, використовуйте Files.newBufferedReader(..., StandardCharsets.UTF_8) замість «за замовчуванням».
5. Найкращі практики: як не потрапити в пастку
Правило № 1:
ЗАВЖДИ явно вказуйте кодування під час роботи з файлами, особливо якщо файл буде використовуватися на різних комп’ютерах, у різних ОС або надсилатися мережею.
Правило № 2:
Використовуйте сучасні, універсальні кодування — насамперед UTF-8 (StandardCharsets.UTF_8). Лише якщо є особливі вимоги (наприклад, інтеграція зі старою системою), використовуйте інші кодування.
Правило № 3:
Уникайте класів FileReader і FileWriter (вони не дозволяють вказати кодування), замість них використовуйте InputStreamReader, OutputStreamWriter або методи з Files із явним Charset.
Правило № 4:
Перевіряйте результат! Відкривайте записані файли в редакторах із підтримкою різних кодувань, щоб упевнитися, що текст виглядає коректно.
6. Особливості й нюанси: BOM, XML, JSON та інші «веселі» випадки
BOM (Byte Order Mark): іноді файл у UTF-8 починається з «невидимих» байтів (EF BB BF). Більшість сучасних програм їх ігнорує, але деякі можуть показати «кракозябру» на початку рядка або не прийняти файл (наприклад, старі парсери XML/JSON).
XML/HTML: інколи на початку файлу є рядок на кшталт <?xml version="1.0" encoding="UTF-8"?>. Він повідомляє програмі, у якому кодуванні очікувати байти. Але якщо фактичне кодування не збігається з оголошеним — знову «кракозябри».
JSON: за стандартом має бути у UTF-8, але якщо файл створено у Windows-1251, парсер видасть помилку або спотворені дані.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ