CodeGym /Kursy /JAVA 25 SELF /Problemy niezgodności kodowań, typowe błędy

Problemy niezgodności kodowań, typowe błędy

JAVA 25 SELF
Poziom 37 , Lekcja 3
Dostępny

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.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION