1. Objawy błędów
W idealnym świecie programiści zawsze wiedzą, w jakim kodowaniu zapisano plik, i poprawnie je wskazują przy odczycie. Rzeczywistość — to świat, w którym pliki wędrują między Windows, Linuxem, serwerami, edytorami, a każdy na swój sposób interpretuje bajty. W rezultacie napotykamy takie objawy:
- „Krzaki” — zamiast oczekiwanego tekstu widzimy dziwne znaki, znaki zapytania, kwadraciki albo zestaw liter niepodobny do żadnego języka świata.
- Utrata znaków — część tekstu znika albo jest zastępowana przez ?.
- Wyjątki — na przykład MalformedInputException, gdy Java nie potrafi „strawić” bajtów w wybranym kodowaniu.
- Błędy parsowania — program nie potrafi poprawnie przetworzyć pliku, ponieważ słowa kluczowe lub struktury zostały uszkodzone wskutek zniekształceń tekstu.
Oto klasyczny przykład „krzaków” przy odczycie pliku z cyrylicą w nieprawidłowym kodowaniu:
Oczekiwano: Hello, world!
Otrzymano: Привет, мир
To nie nowy język, lecz wynik tego, że bajty zinterpretowano nie tym „słownikiem”, co trzeba.
2. Dlaczego pojawiają się błędy: przyczyna problemu
Plik zapisany w jednym kodowaniu, a odczytywany w innym
Załóżmy, że ktoś zapisał plik w Windows-1251, a ty otwierasz go w UTF-8. Java uczciwie próbuje rozszyfrować bajty zgodnie z zasadami UTF-8, ale wychodzi bezsens, ponieważ wartości bajtów nie pokrywają się z oczekiwanymi.
Korzystanie z systemowego kodowania „domyślnego”
Jeśli nie wskażesz kodowania wprost, Java użyje systemowego — tego, które jest ustawione na twoim komputerze. W Windows z rosyjską lokalizacją może to być Windows-1251, w Linuksie — UTF-8, na Macu — również UTF-8. Plik, który u ciebie otwiera się bez problemu, u kolegi z innym systemem może stać się nieczytelny.
Używanie przestarzałych konstruktorów
W starych wersjach Javy (i w niektórych podręcznikach) często można spotkać konstrukcje z FileReader/FileWriter, które używają systemowego kodowania i nie dają ci kontroli — to pułapka i źródło „krzaków”.
FileReader reader = new FileReader("file.txt");
FileWriter writer = new FileWriter("file.txt");
Obecność lub brak BOM (Byte Order Mark)
Niektóre kodowania (np. UTF-8 z BOM lub UTF-16) dodają na początku pliku specjalne bajty, aby zasygnalizować swoje pochodzenie. Jeśli program nie oczekuje BOM albo przeciwnie — oczekuje go, lecz nie znajduje, mogą pojawić się problemy: albo pierwsze znaki pliku będą zniekształcone, albo plik nie zostanie w ogóle rozpoznany.
3. Jak przejawiają się błędy: analiza w praktyce
Przykład 1: Plik z cyrylicą, zapisany w Windows-1251, czytany jako 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");
// Plik zapisany w Windows-1251, czytamy jako UTF-8 — będą krzaki!
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
System.out.println(reader.readLine());
}
}
}
W rezultacie zamiast „Witaj, świecie!” zobaczysz zestaw dziwnych znaków.
Przykład 2: Plik zapisany w UTF-8, odczytywany jako ISO-8859-1
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.ISO_8859_1)) {
System.out.println(reader.readLine());
}
Rezultat: Wszystkie znaki spoza ASCII zamienią się w śmieci lub zostaną zastąpione przez ?.
Przykład 3: Wyjątek przy odczycie pliku
Jeśli bajty nie odpowiadają zasadom wybranego kodowania, Java może rzucić wyjątek:
Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
...
To znaczy, że Java napotkała bajt, którego nie da się poprawnie zinterpretować w wybranym kodowaniu.
4. Diagnostyka: jak zrozumieć, co jest nie tak z kodowaniem
Sprawdzaj kodowanie pliku
- W edytorach typu Notepad++, VS Code lub Sublime Text zwykle można podejrzeć albo zmienić kodowanie pliku (zwykle na dolnym pasku).
- W Linuksie polecenie może dać wskazówkę co do kodowania (choć nie zawsze w 100% trafną):
file filename.txt
Sprawdzaj systemowe kodowanie w Javie
Wypisz na konsolę wartość właściwości file.encoding:
System.out.println(System.getProperty("file.encoding"));
Używaj danych testowych
Utwórz mały plik z różnymi znakami (cyrylica, łacinka, znaki specjalne, emoji), spróbuj odczytać go z różnymi kodowaniami i sprawdź, kiedy wynik zgadza się z oczekiwaniami.
Zawsze jawnie wskazuj kodowanie
Gdy tylko widzisz w kodzie odczyt/zapis pliku bez wskazania kodowania — to powód do niepokoju. Na przykład użyj Files.newBufferedReader(..., StandardCharsets.UTF_8) zamiast „domyślności”.
5. Dobre praktyki: jak nie wpaść w pułapkę
Zasada nr 1:
ZAWSZE jawnie wskazuj kodowanie przy pracy z plikami, zwłaszcza jeśli plik będzie używany na różnych komputerach, w różnych systemach operacyjnych lub wysyłany przez sieć.
Zasada nr 2:
Używaj nowoczesnych, powszechnych kodowań — przede wszystkim UTF-8 (StandardCharsets.UTF_8). Tylko jeśli istnieją szczególne wymagania (np. integracja ze starym systemem), stosuj inne kodowania.
Zasada nr 3:
Unikaj klas FileReader i FileWriter (nie pozwalają wskazać kodowania); zamiast nich używaj InputStreamReader, OutputStreamWriter lub metod z Files z jawnym Charset.
Zasada nr 4:
Sprawdzaj rezultat! Otwieraj zapisane pliki w edytorach obsługujących różne kodowania, aby upewnić się, że tekst wygląda poprawnie.
6. Szczegóły i niuanse: BOM, XML, JSON i inne „wesołe” przypadki
BOM (Byte Order Mark): czasem plik w UTF-8 zaczyna się od „niewidocznych” bajtów (EF BB BF). Większość współczesnych programów je ignoruje, ale niektóre mogą pokazać „krzaki” na początku wiersza lub odrzucić plik (np. stare parsery XML/JSON).
XML/HTML: czasem na początku pliku znajduje się wiersz w rodzaju <?xml version="1.0" encoding="UTF-8"?>. Informuje on program, w jakim kodowaniu należy oczekiwać bajtów. Jednak jeśli faktyczne kodowanie nie zgadza się z zadeklarowanym — znów pojawią się „krzaki”.
JSON: zgodnie ze standardem powinien być w UTF-8, ale jeśli plik utworzono w Windows-1251, parser zwróci błąd albo zniekształcone dane.
GO TO FULL VERSION