CodeGym /Kursy /JAVA 25 SELF /Czym jest kodowanie znaków i po co jest potrzebne

Czym jest kodowanie znaków i po co jest potrzebne

JAVA 25 SELF
Poziom 37 , Lekcja 0
Dostępny

1. Pojęcie kodowania znaków (encoding)

Zacznijmy od najważniejszego pytania: czym jest kodowanie znaków?

Wyobraźmy sobie, że jedziesz na międzynarodową konferencję. Każdy mówi w swoim języku, ale wszyscy chcą się zrozumieć. Potrzebny jest tłumacz, który wie, że słowo "hello" po rosyjsku to "privet", a po hiszpańsku "hola". W świecie komputerów kodowanie znaków jest właśnie takim tłumaczem.

Kodowanie znaków — to sposób reprezentacji znaków jako bajtów

Komputer to proste urządzenie: rozumie tylko zera i jedynki, czyli bity i bajty. Ludzie chcą jednak widzieć litery, cyfry, emoji i nawet — o, zgrozo! — chińskie ideogramy. Aby komputer mógł „zapisać” znak, trzeba uzgodnić, jakiemu ciągowi bajtów będzie odpowiadał każdy znak.

Kodowanie znaków (encoding) — to zestaw reguł, według których znaki (litery, cyfry, znaki interpunkcyjne, emoji itd.) zamieniają się na bajty do przechowywania i transmisji, i odwrotnie: bajty zamieniają się na znaki do wyświetlania.

Przykład: litera 'A' cyrylicka (U+0410) w różnych kodowaniach

  • W kodowaniu UTF-8 litera 'A' cyrylicka (U+0410) jest kodowana dwoma bajtami: 0xD0 0x90.
  • W kodowaniu Windows-1251 ta sama litera — jednym bajtem: 0xC0.
  • Natomiast łacińska 'A' w niemal wszystkich popularnych kodowaniach to 0x41.

Jeśli odczytasz plik w niewłaściwym kodowaniu, znaki zamienią się w „krzaczki” (niezrozumiałe symbole lub znaki zapytania).

2. Po co są potrzebne kodowania znaków

Dlaczego nie można po prostu przechowywać liter tak, jak są?

Bo komputer rozumie tylko liczby (zera i jedynki). To, jaka liczba odpowiada której literze — to właśnie istota kodowania.

Przykład: „Hello” na dysku

Gdy zapisujesz do pliku słowo "Hello", dla komputera to tylko sekwencja bajtów. To, jak te bajty interpretować, zależy od kodowania.

  • Jeśli plik zapisano w UTF-8, każda cyrylicka litera zajmuje dwa bajty i tak dalej.
  • Jeśli w Windows-1251, każda litera — jeden bajt, ale wartości bajtów są inne.

Gdzie potrzebne jest kodowanie znaków?

  • Przy zapisie tekstu do pliku: aby bajty można było później poprawnie odczytać.
  • Przy odczycie tekstu z pliku: aby bajty zostały z powrotem zamienione na litery.
  • Przy wysyłaniu tekstu przez sieć (np. HTTP, e‑mail).
  • Przy pracy z bazami danych: tam także trzeba wiedzieć, w jakim kodowaniu jest przechowywany tekst.

Jeśli nie określisz kodowania...

To jak otworzyć tekst w nieznanym języku i próbować go czytać. Szanse rosną, jeśli wiesz, jaki to język. Jeśli nie — w najlepszym razie nic nie zrozumiesz, w najgorszym dostaniesz bełkot.

3. Problemy bez właściwego kodowania

„Krzaczki” i utrata danych

Najczęstsza skarga początkujących (i nie tylko) programistów: „Dlaczego zamiast "Hello" widzę "Привет" albo nawet same znaki zapytania?”

Dzieje się tak, gdy plik został zapisany w jednym kodowaniu, a odczytywany jest w innym. Na przykład plik powstał na starym Windowsie w Windows-1251, a otwierasz go w Linuksie, gdzie domyślnie jest UTF-8. Albo odwrotnie.

Przykład

  • Plik zapisano w Windows-1251: bajt dla cyrylickiej litery (np. U+041F) — 0xCF.
  • Otworzono w UTF-8: program oczekuje, że cyrylica to dwa bajty, a dostaje jeden. Wszystko się sypie.

Utrata danych

Jeśli przy zapisie znak nie jest obsługiwany przez wybrane kodowanie (na przykład próbujesz zapisać emoji w ASCII), to zniknie lub zostanie zastąpiony znakiem zapytania. Wszystko, co nie „mieści się” w kodowaniu, przepada.

Problemy przy wymianie plików

Pliki zapisane w jednym kodowaniu mogą wyświetlać się niepoprawnie na innych komputerach, jeśli tam domyślne kodowanie jest inne. Często dzieje się tak przy wymianie plików między Windows i Linux lub przy otwieraniu starych plików.

4. Kodowanie znaków w Javie: reprezentacja wewnętrzna i zewnętrzna

Wewnątrz JVM: zawsze Unicode (UTF-16)

W Javie łańcuchy (String) wewnątrz programu są zawsze przechowywane w Unicode (ściślej, w UTF-16). To znaczy, że możesz spokojnie przypisywać zmiennym teksty w dowolnym języku świata, a Java to wszystko „strawi”.

String hello = "Witaj, świecie! 😀";

W pamięci JVM ten tekst jest przechowywany jako zestaw 16‑bitowych liczb (char), gdzie każdemu znakowi odpowiada jego kod w tablicy Unicode.

Ciekawostka
W Javie symbol typu char ma 16 bitów (2 bajty). Jednak niektóre znaki (np. rzadkie ideogramy lub emoji) wymagają już dwóch char — to „pary surogatowe”.

Przy wejściu/wyjściu: kodowanie ma znaczenie!

Kiedy odczytujesz lub zapisujesz łańcuchy do świata zewnętrznego (pliki, sieć), Java musi przekształcić wewnętrzną reprezentację (UTF-16) w sekwencję bajtów. Właśnie wtedy potrzebne jest kodowanie.

  • Jeśli nie wskażesz kodowania wprost, Java użyje domyślnego systemowego (w rosyjskim Windows może to być Windows-1251, w Linuksie — UTF-8).
  • To ryzykowne: na innym komputerze rezultat może być inny.

Przykład: odczyt i zapis pliku bez określenia kodowania

// Zła praktyka! Kodowanie znaków nie zostało podane.
FileReader reader = new FileReader("data.txt");
FileWriter writer = new FileWriter("data.txt");

W tym przypadku Java użyje kodowania systemowego. Jeśli plik został zapisany w innym systemie — zobaczysz „krzaczki”.

Dobra praktyka: zawsze podawaj kodowanie

// Dobrze! Kodowanie zostało jawnie wskazane.
BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("data.txt"), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream("data.txt"), StandardCharsets.UTF_8));

5. Krótka ilustracja: co dzieje się podczas pracy z kodowaniami

Schemat: droga łańcucha znaków od pliku do programu i z powrotem

[Plik na dysku (bajty, kodowanie X)]
        |
        V
[Java czyta bajty i za pomocą kodowania X zamienia je na String (UTF-16)]
        |
        V
[Pracujesz z łańcuchem znaków w programie]
        |
        V
[Java zapisuje String do bajtów, używając kodowania Y]
        |
        V
[Plik na dysku (bajty, kodowanie Y)]

Jeśli X i Y są takie same — wszystko działa. Jeśli różne — możliwe problemy.

6. Krótka historia kodowań (dla ciekawych)

ASCII

ASCII — jedno z najstarszych kodowań: jeden bajt na znak, tylko alfabet angielski, cyfry i podstawowe znaki. Inne alfabety — odpadają.

Windows-1251, ISO-8859-1 i inne „staruszki”

To jednobajtowe kodowania dla różnych zestawów znaków: cyrylica, łacina, greka itd. Każdy wybierał swoje i zaczęło się zamieszanie.

Unicode i rodzina UTF

  • Unicode — globalna tablica dla znaków całego świata.
  • UTF-8, UTF-16, UTF-32 — różne sposoby reprezentacji znaków Unicode w bajtach.
  • UTF-8 stał się standardem dla sieci Web, plików i wymiany między systemami.

7. Praktyka: jak kodowanie wpływa na pracę z plikami

Zobaczmy krótki przykład zapisu i odczytu łańcuchów w różnych kodowaniach.

Przykład: zapis i odczyt w różnych kodowaniach

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class EncodingDemo {
    public static void main(String[] args) throws IOException {
        String text = "Witaj, świecie! 😀";

        // Zapiszmy plik w UTF-8
        try (Writer writer = new OutputStreamWriter(
                new FileOutputStream("utf8.txt"), StandardCharsets.UTF_8)) {
            writer.write(text);
        }

        // Spróbujmy teraz odczytać go w nieprawidłowym kodowaniu
        try (Reader reader = new InputStreamReader(
                new FileInputStream("utf8.txt"), Charset.forName("Windows-1251"))) {
            int c;
            while ((c = reader.read()) != -1) {
                System.out.print((char) c);
            }
        }
        // Na ekranie będą krzaczki!
    }
}

Wniosek: Jeśli kodowania się nie zgadzają — tekst zostanie zniekształcony.

8. Kodowanie znaków a integracja z innymi systemami

W rzeczywistych projektach plikami często wymieniają się różne programy, napisane w różnych językach i działające na różnych systemach operacyjnych. Każdy może oczekiwać innego kodowania. Jeśli nie uzgodnić tego wcześniej — dostaniesz „krzaczki” i trudne do wyłapania błędy. Typowy przypadek: baza danych przechowuje tekst w UTF-8, a program czyta plik źródłowy jako Windows-1251 i ładuje do DB — zniekształcone znaki gwarantowane.

9. Typowe błędy przy pracy z kodowaniem znaków

Błąd nr 1: Brak określonego kodowania przy odczycie/zapisie pliku.
W efekcie program działa „u mnie”, a u kolegi — „krzaczki”.

Błąd nr 2: Używanie przestarzałych konstruktorów (FileReader, FileWriter).
Zawsze używają kodowania systemowego — pułapka dla początkujących.

Błąd nr 3: Nieprawidłowe kodowanie pliku źródłowego.
Jeśli plik zapisano w jednym kodowaniu, a czyta się go w innym, część znaków będzie zniekształcona lub zastąpiona znakami zapytania.

Błąd nr 4: Utrata znaków przy konwersji między kodowaniami.
Jeśli docelowe kodowanie nie obsługuje wszystkich znaków (na przykład ASCII zamiast UTF-8), część tekstu po prostu zniknie.

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