1. UTF-8 — król współczesnych kodowań
W pracy programisty spotkasz pliki, protokoły sieciowe, bazy danych, usługi webowe, w których używa się różnych kodowań. Jeśli nie wiesz, czym UTF-8 różni się od ISO-8859-1, łatwo wpadniesz w pułapkę: twój kod będzie działał „u mnie na komputerze”, ale zepsuje się u kolegi z Francji albo w chmurze, gdzie ustawione jest inne kodowanie domyślne.
Dziś omówimy trzy główne kodowania spotykane w codziennej pracy programisty Java:
- UTF-8 — nowoczesny uniwersalny standard.
- UTF-16 — wewnętrzny format łańcuchów w Javie i często spotykany w Windows.
- ISO-8859-1 (Latin-1) — stare, ale wciąż używane jednobajtowe kodowanie dla języków zachodnioeuropejskich.
Przyjrzyjmy się im z bliska!
Co to jest?
UTF-8 (Unicode Transformation Format, 8-bit) to kodowanie, które potrafi przedstawić dowolny znak z ogromnej tabeli Unicode (to m.in. cyrylica, chińskie ideogramy, emoji, a nawet rzadkie dawne pisma). Robi to sprytnie: dla najpopularniejszych znaków (angielskie litery, cyfry, znaki interpunkcyjne) używa zaledwie 1 bajtu. Dla pozostałych — 2, 3 albo nawet 4 bajty.
Fakt: Wszystkie współczesne strony, większość plików JSON i XML, a nawet źródła w Javie, domyślnie używają UTF-8.
Ile bajtów potrzeba na jeden znak?
- Znaki ASCII (angielskie litery, cyfry, znaki interpunkcyjne) — 1 bajt.
- Znaki cyrylicy, europejskie znaki z diakrytykami — 2 bajty.
- Chińskie, japońskie, koreańskie ideogramy — 3 bajty.
- Bardzo egzotyczne znaki, emoji — 4 bajty.
Przykład:
| Znak | Kod w Unicode | UTF-8 (w bajtach) |
|---|---|---|
| A | |
|
| Ya | |
|
| € | |
|
| 😀 | |
|
Dlaczego UTF-8 jest świetny?
- Wstecznie zgodny z ASCII: jeśli plik zawiera tylko angielskie znaki, wygląda identycznie jak w ASCII.
- Kompaktowość: dla krótkich tekstów po angielsku plik w UTF-8 ma minimalny rozmiar.
- Międzynarodowość: obsługuje wszystkie języki świata, w tym emoji.
- Standard dla sieci WWW: HTML, CSS, JSON, XML, JavaScript — to wszystko domyślnie UTF-8.
Przykład użycia w Javie
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class Utf8Demo {
public static void main(String[] args) throws Exception {
// Zapis łańcucha do pliku w UTF-8
String text = "Witaj, świecie! 😀";
Files.write(Paths.get("utf8.txt"), text.getBytes(StandardCharsets.UTF_8));
// Odczyt pliku w UTF-8
String read = Files.readString(Paths.get("utf8.txt"), StandardCharsets.UTF_8);
System.out.println(read); // Wypisze: Witaj, świecie! 😀
}
}
Tutaj najpierw jawnie kodujemy łańcuch w UTF-8 przed zapisem, a potem równie jawnie podajemy kodowanie przy odczycie. Dzięki temu tekst zachowuje się i odtwarza bez zniekształceń, nawet jeśli zawiera emoji lub znaki z różnych języków.
Kiedy używać UTF-8?
Zawsze, chyba że masz bardzo mocny powód, by wybrać coś innego. To uniwersalny wybór dla wszystkich nowoczesnych aplikacji.
2. UTF-16 — wewnętrzny format Javy
UTF-16 to kolejne kodowanie z rodziny Unicode. W odróżnieniu od UTF-8 najczęściej używa 2 bajtów na znak. Właśnie tak Java przechowuje łańcuchy (String) i znaki (char) wewnątrz JVM. Jest jednak niuans: niektóre znaki (np. rzadkie ideogramy lub emoji) wymagają już 4 bajtów (dwie „pary surogatowe”).
Gdzie używa się UTF-16?
- Wewnątrz Javy: wszystkie łańcuchy i znaki — to UTF-16.
- Windows: wiele plików systemowych w Windows używa UTF-16 (np. Notepad domyślnie zapisuje w UTF-16 LE).
- Niektóre protokoły i formaty: na przykład XML może być w UTF-16.
Przykład: jak wygląda znak w UTF-16
| Znak | Kod w Unicode | UTF-16 (bajty) | Komentarz |
|---|---|---|---|
| A | |
|
2 bajty |
| Ya | |
|
2 bajty |
| € | |
|
2 bajty |
| 😀 | |
|
4 bajty (para surogatowa) |
Przykład użycia w Javie
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class Utf16Demo {
public static void main(String[] args) throws Exception {
String text = "Witaj, świecie! 😀";
// Zapis łańcucha do pliku z użyciem kodowania UTF-16
Files.write(Paths.get("utf16.txt"), text.getBytes(StandardCharsets.UTF_16));
// Odczyt zawartości pliku z podaniem kodowania UTF-16
String read = Files.readString(Paths.get("utf16.txt"), StandardCharsets.UTF_16);
System.out.println(read); // Poprawne wyświetlenie oryginalnego łańcucha
}
}
Kluczowa kwestia: przy zapisie i odczycie trzeba podawać to samo kodowanie. Jeśli zapiszesz w UTF-16, a odczytasz domyślnie (np. w UTF-8), pojawią się „krzaki” i dziwne znaki.
Cechy UTF-16
- Stała długość dla większości znaków: 2 bajty.
- Pary surogatowe: część znaków (np. emoji) wymaga 4 bajtów.
- Byte Order Mark (BOM): na początku pliku często dodawana jest specjalna „główka”, wskazująca porządek bajtów (LE — little endian, BE — big endian). Java zwykle sama rozpoznaje BOM.
- Niezgodny z ASCII: jeśli otworzysz plik UTF-16 w edytorze, który oczekuje ASCII lub UTF-8, zobaczysz mnóstwo zer i dziwnych znaków.
Kiedy używać UTF-16?
- Gdy pracujesz z bardzo specyficznymi systemami lub plikami, które wymagają dokładnie UTF-16 (np. integracja z programami Windows).
- Dla zwykłych aplikacji i plików — lepiej używać UTF-8.
4. ISO-8859-1 (Latin-1) — pozdrowienia z przeszłości
ISO-8859-1, czyli Latin-1 — to jednobajtowe kodowanie stworzone jeszcze w latach 80. ubiegłego wieku. Obsługuje 256 znaków: angielski alfabet, zachodnioeuropejskie litery z diakrytykami (é, ü, ç itp.), znaki interpunkcyjne, znaki specjalne.
Fakt: W tym kodowaniu nie ma cyrylicy, greki, arabskiego, chińskiego ani innych nielatynicznych pism.
Gdzie spotyka się ISO-8859-1?
- W starych plikach i programach (zwłaszcza w Europie).
- W niektórych bazach danych i protokołach „domyślnie”.
- W nagłówkach HTTP (zgodnie ze standardem dane tekstowe bez wskazania kodowania uznaje się za ISO-8859-1, choć w praktyce to rzadkość).
Przykład: jak wygląda znak w ISO-8859-1
| Znak | Kod w Unicode | ISO-8859-1 (bajt) | Komentarz |
|---|---|---|---|
| A | |
|
Tak jak w ASCII |
| é | |
|
Francuski |
| ü | |
|
Niemiecki |
| Ya | |
— | Brak takiego znaku! |
Przykład użycia w Javie
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é!"; // Tekst francuski
Files.write(Paths.get("latin1.txt"), text.getBytes(StandardCharsets.ISO_8859_1));
// Odczyt pliku w ISO-8859-1
String read = Files.readString(Paths.get("latin1.txt"), StandardCharsets.ISO_8859_1);
System.out.println(read); // Wszystko odczytuje się poprawnie!
// Spróbujmy zapisać cyrylicę
String cyrillic = "Hello, world!";
try {
Files.write(Paths.get("badlatin1.txt"), cyrillic.getBytes(StandardCharsets.ISO_8859_1));
} catch (Exception e) {
System.out.println("Błąd: nie można zapisać cyrylicy w ISO-8859-1!");
}
}
}
Kodowanie ISO-8859-1 (Latin-1) obsługuje tylko znaki zachodnioeuropejskie — na przykład litery z akcentami z języka francuskiego, niemieckiego czy hiszpańskiego. Dlatego tekst francuski zapisuje się i odczytuje bez problemów.
Jednak cyrylicy w tym kodowaniu nie ma i przy próbie jej zapisu wystąpi błąd albo utrata znaków. Dlatego do tekstów wielojęzycznych lepiej używać UTF-8 lub UTF-16, które obejmują znacznie większy zbiór znaków.
Cechy ISO-8859-1
- Ograniczenia językowe: tylko języki zachodnioeuropejskie.
- 1 bajt na znak: kompaktowo, ale bardzo ograniczająco.
- Zapisywanie „obcych” znaków prowadzi do zniekształceń: jeśli spróbujesz zapisać cyrylicę lub ideogramy, zamienią się w znaki zapytania albo śmieci.
Kiedy używać ISO-8859-1?
- Tylko gdy integrować się musisz z bardzo starą aplikacją lub bazą danych, która wymaga dokładnie tego kodowania.
- Do nowoczesnych zadań — niezalecane.
5. Przydatne szczegóły
Tabela porównawcza kodowań
| Kodowanie | Bajty na znak | Jakie języki | Zgodność z ASCII | Gdzie używane |
|---|---|---|---|---|
|
1-4 | Wszystkie | Pełna | WWW, Java, JSON, XML, Linux |
|
2 lub 4 | Wszystkie | Nie | Wewnątrz Javy, Windows, XML |
|
1 | Zachodnia Europa | Pełna | Stare programy, bazy, HTTP |
Jak wybrać kodowanie?
- UTF-8 — twój wybór dla 99 % zadań. Zgodny z ASCII, obsługuje wszystkie języki, minimalny rozmiar dla tekstu angielskiego, standard dla WWW i Javy.
- UTF-16 — używaj tylko, jeśli wymaga tego specyfikacja lub integracja z programami Windows.
- ISO-8859-1 — tylko dla kompatybilności wstecznej ze starymi systemami. Nigdy nie używaj do przechowywania cyrylicy, greki, arabskiego itd.
Jak sprawdzić, w jakim kodowaniu jest plik?
- Otwórz plik w edytorze, który pokazuje kodowanie (np. Notepad++, VS Code).
- Jeśli widzisz „krzaki”, spróbuj otworzyć plik w innym kodowaniu.
- W terminalu Linux możesz użyć polecenia file nazwa_pliku — czasem podpowiada kodowanie.
Jak ustawić kodowanie w Javie?
Używaj klas z pakietu java.nio.charset:
- StandardCharsets.UTF_8
- StandardCharsets.UTF_16
- StandardCharsets.ISO_8859_1
Albo przez nazwę kodowania:
Charset windows1251 = Charset.forName("Windows-1251");
Charset utf8 = Charset.forName("UTF-8");
6. Praktyka: co się stanie, gdy pomylisz kodowanie?
Zróbmy mały eksperyment. Zapiszemy łańcuch w UTF-8, a potem spróbujemy odczytać go jako ISO-8859-1:
import java.nio.file.*;
import java.nio.charset.*;
public class EncodingMismatchDemo {
public static void main(String[] args) throws Exception {
String text = "Hello, world!"; // Cyrylica
// Zapisujemy jako UTF-8
Files.write(Paths.get("utf8demo.txt"), text.getBytes(StandardCharsets.UTF_8));
// Czytamy jako ISO-8859-1 (NIEPRAWIDŁOWO!)
String wrong = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.ISO_8859_1);
System.out.println("Nieprawidłowy odczyt: " + wrong);
// Teraz czytamy poprawnie
String correct = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.UTF_8);
System.out.println("Prawidłowy odczyt: " + correct);
}
}
Wynik:
- W pierwszym przypadku zobaczysz „krzaki” — zestaw dziwnych znaków.
- W drugim przypadku — poprawny tekst po rosyjsku.
7. Typowe błędy przy pracy z kodowaniami
Błąd №1: Odczyt pliku w nieprawidłowym kodowaniu. Jeśli plik zapisano w UTF-8, a ty czytasz go jako ISO-8859-1 lub Windows-1251, wszystkie znaki spoza ASCII zmienią się w śmieci. To klasyk: „u mnie działa, a u kolegi — nie”.
Błąd №2: Próba zapisania „obcych” znaków w jednobajtowym kodowaniu. Jeśli spróbujesz zapisać cyrylicę lub ideogramy w ISO-8859-1, Java zastąpi je znakami zapytania albo zgłosi błąd. Dane zostaną bezpowrotnie utracone.
Błąd №3: Użycie systemowego kodowania „domyślnego”. W niektórych metodach Javy (np. new FileReader("file.txt")) kodowania nie podaje się jawnie — używane jest systemowe. Na jednym komputerze może to być UTF-8, na innym — Windows-1251, a na jeszcze innym — coś egzotycznego. Dlatego zawsze używaj metod, w których można jawnie wskazać kodowanie.
Błąd №4: Otwarcie pliku UTF-16 jako UTF-8. Plik zapisany w UTF-16, jeśli otworzyć go jako UTF-8, będzie pełen zer i dziwnych znaków, ponieważ bajty zostaną zinterpretowane nieprawidłowo.
GO TO FULL VERSION