CodeGym /Kursy /JAVA 25 SELF /Podstawowe kodowania znaków: UTF-8, UTF-16, ISO-8859-1

Podstawowe kodowania znaków: UTF-8, UTF-16, ISO-8859-1

JAVA 25 SELF
Poziom 37, Lekcja 1
Dostępny

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
U+0041
41
Ya
U+042F
D0 AF
€
U+20AC
E2 82 AC
😀
U+1F600
F0 9F 98 80

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
U+0041
00 41
2 bajty
Ya
U+042F
04 2F
2 bajty
€
U+20AC
20 AC
2 bajty
😀
U+1F600
D8 3D DE 00
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
U+0041
41
Tak jak w ASCII
é
U+00E9
E9
Francuski
ü
U+00FC
FC
Niemiecki
Ya
U+042F
— 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
UTF-8
1-4 Wszystkie Pełna WWW, Java, JSON, XML, Linux
UTF-16
2 lub 4 Wszystkie Nie Wewnątrz Javy, Windows, XML
ISO-8859-1
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.

1
Zadanie
JAVA 25 SELF, poziom 37, lekcja 1
Niedostępne
Próba wysłania cyrylicy przez przestarzały system komunikacji 📟
Próba wysłania cyrylicy przez przestarzały system komunikacji 📟
1
Zadanie
JAVA 25 SELF, poziom 37, lekcja 1
Niedostępne
Eksperyment lingwistyczny: Waga słów w różnych kodowaniach ⚖️
Eksperyment lingwistyczny: Waga słów w różnych kodowaniach ⚖️
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION