1. Wprowadzenie
Powiedzmy to wprost: jeśli choć raz widzieliście zamiast rosyjskiego tekstu coś w rodzaju Привет, padliście ofiarą niewłaściwego kodowania. Dzieje się tak, gdy plik został zapisany w jednym kodowaniu, a odczytywany w innym. Na przykład plik zapisano jako UTF-8, a czytany jest jako Windows-1251, lub odwrotnie.
Java domyślnie używa systemowego kodowania, które można sprawdzić tak:
System.out.println(System.getProperty("file.encoding"));
Na jednym komputerze może to być UTF-8, na innym — Windows-1251, a gdzie indziej — ISO-8859-1. Dlatego zawsze lepiej wskazywać kodowanie wprost. Jest to szczególnie ważne, jeśli pracujesz z danymi wielojęzycznymi, jeśli pliki będą używane na różnych komputerach lub otwierane w innych programach, albo jeśli chcesz, by twój kod zachowywał się tak samo w każdym środowisku, a nie tylko na twojej maszynie.
Klasa Charset: twój przyjaciel w świecie kodowań
W Javie do pracy z kodowaniami służy klasa java.nio.charset.Charset. Pozwala wskazać kodowanie po nazwie (na przykład "UTF-8") oraz używać standardowych stałych (StandardCharsets.UTF_8).
Przykłady standardowych kodowań:
| Kodowanie | Stała w Javie |
|---|---|
| UTF-8 | |
| UTF-16 | |
| ISO-8859-1 | |
| Windows-1251 | |
Używanie stałych jest preferowane: mniejsze ryzyko pomyłki w nazwie i nie wystąpi UnsupportedCharsetException.
2. Odczyt plików z podaniem kodowania
Stary sposób:
import java.io.*;
import java.nio.charset.StandardCharsets;
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("example.txt"),
StandardCharsets.UTF_8 // <-- Jawnie podajemy kodowanie
)
);
Nowoczesny sposób:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedReader;
BufferedReader reader = Files.newBufferedReader(
Paths.get("example.txt"),
StandardCharsets.UTF_8 // <-- Jawnie podajemy kodowanie
);
Zaleca się używać drugiego sposobu — jest krótszy, bezpieczniejszy i wygodnie łączy się z try-with-resources.
Przykład: czytamy wiersz z pliku
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("hello.txt"),
StandardCharsets.UTF_8)) {
String line = reader.readLine();
System.out.println("Przeczytano: " + line);
}
Dlaczego to ważne: Jeśli plik został zapisany w UTF-8, a czytasz go jako Windows-1251, znaki cyryliczne zostaną zniekształcone. Jeśli podasz właściwe kodowanie, tekst będzie odczytywany poprawnie na dowolnym systemie operacyjnym.
3. Zapis plików z podaniem kodowania
Stary sposób:
import java.io.*;
import java.nio.charset.StandardCharsets;
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("example.txt"),
StandardCharsets.UTF_8 // <-- Jawnie podajemy kodowanie
)
);
Nowoczesny sposób:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedWriter;
BufferedWriter writer = Files.newBufferedWriter(
Paths.get("example.txt"),
StandardCharsets.UTF_8 // <-- Jawnie podajemy kodowanie
);
Przykład: zapisujemy wiersz do pliku
try (BufferedWriter writer = Files.newBufferedWriter(
Paths.get("hello.txt"),
StandardCharsets.UTF_8)) {
writer.write("Witaj, świecie!");
}
Wynik: Plik zostanie zapisany w UTF-8 i będzie można go poprawnie otworzyć w każdym edytorze obsługującym UTF-8.
4. Przydatne szczegóły
Jak sprawdzić obsługiwane kodowania
import java.nio.charset.Charset;
public class ListCharsets {
public static void main(String[] args) {
System.out.println("Dostępne kodowania:");
Charset.availableCharsets().forEach((name, charset) -> System.out.println(name));
}
}
Wskazówka: Jeśli używasz egzotycznego kodowania (na przykład dla starożytnych chińskich znaków lub marsjańskich emotikonów), sprawdź, czy jest ono obsługiwane przez twoją JVM.
Użycie try-with-resources: nie zapominaj o zamykaniu strumieni
Pracując z plikami, ważne jest zamykanie strumieni, aby nie było wycieków zasobów. Nowoczesny kod Java używa konstrukcji try-with-resources:
try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
// Pracujemy z plikiem
}
Strumień zamknie się automatycznie, nawet jeśli wystąpi błąd.
Zalecenia
- Lepiej zawsze jawnie podawać kodowanie przy odczycie i zapisie plików, nawet jeśli jesteś pewien, że „domyślnie wszystko działa”.
- Używaj UTF-8 dla nowych plików — to de facto standard, zwłaszcza jeśli pracujesz z web, JSON, XML, lub chcesz, aby twoje pliki były czytelne wszędzie.
- Dla starych plików (np. eksport z 1C, starych baz danych, CSV z Windows) używaj tego kodowania, w którym zostały zapisane (na przykład Windows-1251, ISO-8859-1).
- Nie używaj przestarzałych klas, w których nie podaje się kodowania wprost: FileReader/FileWriter. Zamiast nich stosuj InputStreamReader/OutputStreamWriter z jawnym kodowaniem lub metody z Files.
- Dla dużych plików używaj buforowania (BufferedReader/BufferedWriter), żeby nie zająć całej pamięci.
5. Typowe błędy przy pracy z kodowaniami
Błąd nr 1: Nie podano kodowania przy odczycie/zapisie pliku.
Jeśli nie podasz kodowania, Java użyje systemowego domyślnego ("file.encoding"). Na twojej maszynie wszystko działa, a u kolegi — „krzaczki”.
Błąd nr 2: Niezgodność kodowań przy odczycie i zapisie.
Plik został zapisany w jednym kodowaniu, a czytany w innym. Na przykład plik zapisany w UTF-8, a odczytywany jako Windows-1251 — cyrylica zostaje zniekształcona.
Błąd nr 3: Używanie przestarzałych klas FileReader/FileWriter.
Te klasy nie pozwalają jawnie wskazać kodowania — nie zaleca się ich używania. Zamiast nich używaj InputStreamReader/OutputStreamWriter z podaniem kodowania albo metod z Files.
Błąd nr 4: Błąd w nazwie kodowania.
Na przykład napisano "utf8" zamiast "UTF-8" lub "win1251" zamiast "Windows-1251". Java rzuci UnsupportedCharsetException.
Błąd nr 5: Strumień nie został zamknięty — plik nie został zapisany.
Jeśli nie użyjesz try-with-resources lub jawnie nie zamkniesz strumienia, część danych może nie trafić na dysk.
GO TO FULL VERSION