FAV-ZCU/KIV PPA1/19. Kódování znaků.md

284 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kodování znaků
- Určuje, jakým „číslem“ jsou jednotlivé znaky (a řetězce složené ze znaků) reprezentovány
### Základní pojmy
- Terminologie není jednotná, liší se u velkých firem a organizací
- Stejné pojmy se používají pro různé věci
- Pro stejné věci existují různé pojmy
- V dalším textu bude využita terminologie z Unicode Character Encoding Model
- Viz https://www.unicode.org/reports/tr17/
#### Organizace kódování znaků
- Pro kódování znaků lze použít několikaúrovňovou organizaci
- Znaková sada (množina kódovaných znaků Coded Character Set CCS)
- Mapování mezi množinou (abstraktních) znaků a množinou nezáporných celých čísel (kódové body code points)
- Např. ```A = 41``` (abstraktní znak ```A``` má kódový bod ```41``` (hexadecimálně))
- Příklady znakových sad jsou US-ASCII, ISO-8859-1, Unicode
- Forma kódování znaků (Character Encoding Form CEF)
- Mapování množiny nezáporných celých čísel (prvků CCS) na množinu kódových jednotek dané šířky (např. 32bitových hodnot ```int```)
- Např. ```A = 00 00 00 41``` (znak ```A``` namapovaný na 32bitový ```int```)
- Kódovací schéma (Character Encoding Scheme CES)
- Alternativní název charset budeme používat dále, odpovídá třídě v ```Javě java.nio.charset.Charset```
- Způsob mapování kódových jednotek z CEF do posloupnosti bytů
- Např. ```A = 00 41``` (znak ```A``` pro znakovou sadu Unicode a kódovací schéma UTF-16BE)
- Pro jednu znakovou sadu může existovat více charsetů
- Např. pro znakovou sadu Unicode existují charsety UTF-8, UTF16 a UTF-32
- Často ale pro jednu znakovou sadu existuje jen jeden charset
- Např. ISO-8859-2
ěžně je jeden soubor napsán v jednom charsetu
- Teoreticky je ale možné, aby byl jeden soubor současně napsán
- Ve více znakových sadách (např. Unicode, ISO-8859-2)
- Za použití více charsetů (UTF-8, ISO-8859-2)
- Kombinací obou předchozích nejhorší případ
- Různé soubory jsou běžně v různých charsetech
#### Trocha historie
- Sedmibitový ASCII kód (US-ASCII)
- American Standard Code for Information Interchage
- Základem většiny kódování používaných v současnosti (alespoň v Evropě a Americe)
- Protože data se standardně ukládají po bytech, je osmý (nejdůležitější) bit (Most Significant Bit MSB ten nejvíc vlevo) vždy 0
- Umožňuje uložit 128 znaků
- Stačí pouze pro čistě anglické texty
- Nestačí pro uložení dalších znaků jiných jazyků (např. diakritické znaky češtiny) používajících latinku a už vůbec ne pro uložení znaků jiných abeced
- 8bitové kódy založené na ASCII
- Použila se zbývající polovina rozsahu jednoho bytu (MSB roven 1), což dává dalších 128 znaků
- Protože jeden znak je jeden byte, není třeba používat speciální charset (kódovací schéma)
- Znaková sada má stejný název jako charset
- 128 znaků navíc ale nestačí pro požadavky všech jazyků (ani pro všechny jazyky používající latinku) najednou
- Vzniklo mnoho charsetů, každý reprezentující specifické požadavky jednoho či skupiny jazyků)
- I pro jeden jazyk vzniklo více (různých) charsetů
- Vznikají charsety dle normy ISO ISO-8859-1 až ISO-8859-15 (pro různé skupiny jazyků)
- Vznikají charsety v národních standardizačních organizacích
- Vznikají propietární charsety, které jsou platformově závislé (Cp1250 pro Windows, MacCentralEurope)
- 16bitový CEF
- Řeší problém s nedostatečným počtem znaků použitím 16 bitů (2 bytů) pro jeden znak => to dává až 65536 možných znaků
- Na vývoji pracují cca od roku 1990 paralelně dvě organizace
- Unicode Consortium
- Znaková sada Unicode
- ISO (International Organization for Standardization)
- Znaková sada ISO/EIC 10646, ve zkratce UCS (Universal Character Set)
- Pro běžné použití netřeba rozlišovat, jednotlivé znaky mají stejné kódové body
- 32bitový CEF
- Při zahrnutí ideografických písem (typicky asijská písma) však ani 65536 znaků není dostatečné množství
- Řeší se použitím 32 bitů (4 bytů) pro jeden znak => to dává teoreticky přes 4 × 10**9 možných znaků, kdy se však nevyužívá celý rozsah
#### Problém pojmenování charsetů
- Je běžné, že jeden charset má několik jmen, které se od sebe částečně nebo zcela liší
- Způsobeno tím, že charsety nově pojmenovávají i různí výrobci (HW a/nebo SW), i když už jméno charsetu existuje
- Např. US-ASCII má 14 dalších evidovaných jmen
- ISO646-US, IBM367, ASCII, cp376, default, ascii7, ANSI_X3.4-1986, iso-ir-6, us, 646, iso_646.irv:1983, csASCII, ANSI_X3.4-1968, ISO_646.irv:1991
- Pořádek zavádí IANA (Internet Assigned Numbers Authority)
- Rozlišuje se základní pojmenování (nejoficiálnější), tzv. kanonické jméno, a ostatní evidovaná jména, tzv. aliasy
- Např. US-ASCII je kanonické jméno a iso-ir-6 je jeho evidovaný alias
- Může se stát, že jméno charsetu není evidováno v IANA, ale charset je podporován některými aplikacemi
- Pak se používá stejný princip kanonického jména a aliasů, ale kanonické jméno musí začínat „x-“ nebo „X-“
- Např. Java Core API podporuje charset x-MacCentralEurope s aliasem MacCentralEurope
### Jednobytové kódy
- Ačkoliv se čím dál více užívá Unicode, stále se můžeme setkat s použitím jednobytových kódu
- Především historické soubory, které (dosud) nebyly převedeny do Unicode
#### US-ASCII (ASCII)
- Většina (nejen) jednobytových kódů vychází ze US-ASCII
- Původně využito pouze 7 bitů
- ```00``` až ```1F``` (0 až 31) řídící znaky (např. ```<CR>```, ```<LF>``` atd.)
- ```20``` (32) mezera
- ```21``` až ``2F`` (33 až 47) interpunkce (např. „!“, „,“, „"“ atd.)
- ```30``` až ```39``` (48 až 57) číslice „0“ až „9“
- ```3A``` až ```40``` (58 až 64) další znaky (např. „;“, „:“, „<“, „@“ atd.)
- ```41``` až ```5A``` (65 až 90) velká písmena „A“ až „Z“
- ```5B``` až ``60`` (91 až 96) další znaky (např. „^“, „[“, atd.)
- ```61``` až ```7A``` (91 až 122) malá písmena „a“ až „z“
- ```7B``` až ```7F``` (123 až 127) další znaky (např. „{“, „~“, „|“ atd.)
- I v anglicky mluvících zemích se začalo využívat dalších 128 znaků (MSB roven 1), ač nebyly potřeba pro běžné znaky anglické abecedy
- Využití mj. pro znaky s čárami umožňující vykreslení „grafických“ oken v textovém prostředí
#### Různá jednobytová kódování češtiny
- Stejně jako pro mnoho jiných jazyků i pro češtinu vznikl jednobytový kód vycházející z ASCII
- Prvních 128 znaků stejných jako US-ASCII (MSB roven 0)
- Dalších 128 znaků použito pro diakritické a další znaky (MSB roven 1)
- Bohužel vzniklo hned několik kódů, které se vzájemně liší pozicí některých znaků s diakritikou
- Běžněji se vyskytujících je cca 11
- Pokud předpokládáme, že soubor je uložen v jednom kódování a ve skutečnosti je uložen v jiném, některé znaky s diakritikou nebudou zobrazeny správně (tj. budou místo nich zobrazeny jiné znaky)
- Některá kódování se liší jen v několika málo znacích, proto je možné si nesrovnalostí na první pohled nevšimnout
- Jednobytová kódování pro češtinu (a slovenštinu a další středo a východoevropské jazyky), se kterými je možné se setkat
- ISO-8859-2 Latin Alphabet No. 2
- Aliasy ibm912, l2, ibm-912, ISO_8859-2, latin2, csISOLatin2, iso8859_2, 912, 8859_2, ISO8859-2, iso-ir-101
- Základní charset pro východoevropské země mezinárodní standard dle ISO
- Dříve se používalo na Linuxu téměř výhradně (dnes na Linuxu většinou nahrazeno UTF-8)
- windows-1250 Windows Eastern European
- Aliasy cp1250, cp5346
- Proprietární charset firmy Microsoft
- Podporován operačními systémy (Windows) a aplikacemi této firmy
- Od ISO-8859-2 se v češtině liší pouze ve znacích „š“, „Š“, „ž“, „Ž“, „ť“, „Ť“
- IBM852 MS-DOS Latin-2 (POZOR! Liší se od ISO-8859-2 ale i od windows1250)
- Aliasy 852, ibm-852, csPCp852, ibm852
- Proprietární charset firmy IBM
- Používaný charset v českém MS-DOS
- Stále používaný implicitní charset v konzoli českých Windows
- Obzvláště bizardní situace, kdy konzole používá IBM852 a zbytek systému používá windows-1250
- Důvod, proč mohou nastat problémy s diakritickými znaky při jejich vstupu/výstupu z/do konzole
- Java předpokládá na standardním vstupu/výstupu charset operačního systému (což je windows-1250), ale konzole používá IBM852
- Od verze Java 1.8 zlepšení, ale stále nefunguje univerzálně
- x-MacCentralEurope Macintosh Latin-2
- Alias MacCentralEurope
- Proprietární charset firmy Apple
### Unicode
- Řeší problému s nedostatkem znaků použitím více bitů (bytů)
- Původně (verze 1.0, 1991) 16 bitů (2 byty) až 65536 znaků
- Brzy se ukázalo (verze 2.0, 1996), že 16 bitů není dost a přešlo se na 32 bitů teoreticky přes 4 × 109 možných znaků, ale v současnosti se neplánuje využití více než 21 bitů
- Prvních 128 znaků mají stejné kódové body (code points) jako znaky v 7bitové US-ASCII
- Kódové body (code points) jednotlivých znaků se označují jako ```U+hexaČíslo```
- ```hexaČíslo``` jsou typicky 4 hexadecimální číslice, může jich být až 6 (vzhledem k uvažovanému rozsahu maximálně 21 bitů každé 2 číslice reprezentují 1 byte)
- Např. ```U+0041``` je znak „```A```“ odpovídá zápisu ```\u0041``` v Javě
#### Současné rozdělení rozsahu znaků Unicode
- Ze 32 bitů se využívá pouze 21 bitů
- Konkrétně hodnoty ```U+000000``` až ```U+10FFFF```
- Tento rozsah je rozdělen na 17 skupin (sfér planes), každá o velikosti 65536 znaků => celkem přes 10**6 znaků
- Původní sada znaků, která se vejde do 16 bitů (2 byty) se označuje jako BMP (Basic Multilingual Plane)
- Je první v pořadí, rozsah ```U+000000``` až ```U+00FFFF```
- Zahrnuje všechny znaky používané v Evropě a Americe a základní ideografická písma čínštiny, japonštiny a korejštiny (HAN písmo)
- Samotné BMP je vnitřně děleno do bloků, které (na rozdíl od sfér) nemají konstantní velikost
- Např. ASCII (rozsah ```U+000000``` až ```U+00007F```) je tzv. Basic Latin Block
- Dalších 16 sfér v rozsahu ```U+010000``` až ```U+10FFFF``` jsou tzv. doplňkové sféry (supplementary planes), které se v oblasti střední Evropy téměř nikdy nepoužívají
- V současnosti (2022 Unicode verze 15.0) má pět doplňkových sfér přiřazeny znaky a celkem šest sfér je pojmenováno
- V současnosti (2022 Unicode verze 15.0) je namapováno (tj. kódovým bodům jsou přiřazeny znaky) 149697 znaků
- Unicode verze 1.0.1 měl 28327 znaků
#### Kódovací schémata (charsety) Unicode
- Protože znak může být uložen na více bytech, je možné používat více charsetů (kódovacích) schémat
- Unicode má tři základní charsety UTF (Unicode Transformation Format), přičemž dva z nich mají další varianty celkem 7 charsetů
- UTF-8
- UTF-16
- Další varianty UTF-16BE a UTF-16LE přesně specifikující pořadí uložení bytů
- UTF-32
- Další varianty UTF-32BE a UTF-32LE přesně specifikující pořadí uložení bytů
- Všechny charsety jsou schopny uložit celý rozsah Unicode (21 bitů)
- Jednotlivé charsety jsou popsány v Kap. 28.3.4 až 28.3.6
#### Problém pořadí bytů, značka bytového pořadí
- Pokud ukládáme do paměti či souboru vícebytové entity (v tomto případě znaky), je potřeba rozlišit pořadí bytů
- Např. pokud uvažujeme uložení znaku „```A```“ (```U+0041```) na dvou bytech, může být uložen jako
- Little Endian (LE „obrácené uložení“) ```41 00```
- Big Endian (BE „přirozené uložení“) ```00 41```
- Pokud uvažujeme uložení znaku „```A```“ na čtyřech bytech, může být uložen jako
- Little Endian ```41 00 00 00```
- Big Endian ```00 00 00 41```
- Způsob ukládání závisí na platformě (Windows LE), programovacím jazyku (Java vždy BE), aplikaci atd.
- Jaký způsob je použit, je důležité při čtení souboru pro správné načtení vícebytových znaků
- Charsety Unicode mohou pro identifikaci používat počáteční značku bytového pořadí
- Byte Order Mark (BOM)
- Zapisuje se na úplný začátek souboru
- Pro tento účel Unicode definuje dva kódové body
- ```U+FEFF``` pevná mezera nulové délky (zero width no-break space)
- ```U+FFFE``` není kód znaku (not a character code)
- Pro UTF-16 má BOM tvar ```FE FF``` pro BE a``` FF FE``` pro LE
- Pokud je značka načtena správně, pak by se pevná mezera nulové délky neměla ze své podstaty zobrazit
- Pokud je načtena nesprávně (zamění se BE za LE nebo naopak), opět by se neměla zobrazit, protože se jedná o neplatný znak
- BOM se může nebo nesmí vyskytovat, což je dáno definicí konkrétního charsetu
- U UTF-8 má BOM tvar ```EF BB BF```
- Není vyžadována ani doporučována, nicméně není zakázána
- Může nastat problém se zdrojovými soubory ```.java```, které mohou být uloženy v UTF-8
- Překladač javac nepředpokládá na začátku souboru BOM (i když není zakázáno, aby tam byla), některé editory ji tam však umístí program pak nelze přeložit
### UTF-8
- Bylo vytvořeno, aby se znaky Unicode daly zakódovat posloupností bytů, se kterými umí pracovat každá aplikace a každý souborový systém
- Obecně rozšířený a používaný charset
- Např. řetězce v .class souborech jsou uloženy v UTF-8
- Ze zmíněných sedmi charsetů Unicode se UTF-8 používá v Evropě a v Americe pravděpodobně nejčastěji
- Pro texty využívající pouze znaky anglické abecedy je UTF-8 totožné s US-ASCII
- Využívá se jen jeden byte na jeden znak
- Soubory tak zabírají stejně místa, jako kdyby byly kódovány v US-ASCII
- Pro diakritické znaky se využívají dva byty, pro speciálnější znaky z BMP tři byty
- Protože diakritických znaků je např. v českém textu cca 10 %, velikost souboru s českým textem naroste oproti použití jednobytového kódování (např. windows-1250) pouze o cca 10 %
- Pro znaky mimo BMP se využívají čtyři byty
- Základní nevýhoda UTF-8
- Znaky obecně nemají stejnou délku => není možné skočit přímo na určitý znak
- „Přeskoč prvních 20 znaků“
- Princip kódování znaků v UTF-8
- Aby bylo jasné, zda daný znak je uložen jako 1, 2, 3 nebo 4 byty, používá se MSB
- Principiálně je možné zakódovat pomocí UTF až 31 bitů
- ```1111 110u 10vv vvvv 10ww www 10xx xxxx 10yy yyyy 10zz zzzz```
- Princip čtení UTF-8
- Pokud má byte nastaveno MSB na ```1```, pak počet jedničkových bitů za ním udává počet následujících bytů za prvním bytem znaku, přičemž každý následující byte začíná ```10``` (viz Tab. 28.2)
- Pokud nečteme text od začátku a „trefíme“ se doprostřed vícebytového znaku, poznáme to podle bitů ```10``` => pak je třeba přeskočit všechny byty začínající ```10```
- V UTF-8 je zbytečná BOM je jen jedno možné pořadí bytů
- Podle specifikace není ani vyžadována ani doporučena, není však zakázána
- Některé aplikace však BOM u UTF-8 vyžadují a některé s ní naopak mají problémy
#### UTF-16
- Vychází z UCS-2, což je kódování ISO s pevnou šířkou 2 byty na znak, což pokryje celou BMP
- Protože však Unicode přešel na 21 bitů, 2 byty (16 bitů) nestačí (pro znaky mimo BMP)
- Proto nastupuje UTF-16, které některé znaky kóduje 4 byty (tj. dvěma znaky UCS-2)
- Oba „znaky“ se dohromady nazývají zástupné páry (surrogate pairs)
- Mezi UCS-2 a UTF-16 je podobný vztah jako mezi ASCII a UTF-8
- UTF-16 se využívá pro uložení řetězců v operační paměti v Javě
- Při uložení UTF-16 do souborů se využívá BOM pro určení pořadí uložení bytů (LE nebo BE)
- Varianty UTF-16LE a UTF-16BE mají pořadí uložení bytů přímo určeno a BOM nesmí obsahovat
- Pokud BOM přesto obsahují, je ignorována
- Oproti UTF-8 zabírá pro běžné texty psané latinkou téměř dvojnásobek místa
- Pro texty psané pouze znaky anglické abecedy přesně dvojnásobek
#### UTF-32
- Kódování s pevnou šířkou 4 byty na znak
- Každý znak je uložen jako 4 byty
- Na 4 bytech jsou tak přímo uloženy kódové body Unicode
- Pouze 21 bitů je významových
- Prakticky odpovídá UCS-4 (kódování ISO, rovněž 4 byty na znak)
- Při uložení UTF-32 do souboru se rovněž využívá BOM pro určení pořadí uloženy bytů (LE nebo BE)
- Varianty UTF-32LE a UTF-32BE mají pořadí uložení bytů přímo určeno a BOM nesmí obsahovat
- Pokud ho přesto obsahují, je ignorován
### Praktické použití v Javě
- Java vnitřně ukládá řetězce do paměti jako UTF-16, většina souborů je však uložena v UTF-8 nebo v různých jednobytových charsetech
- Při čtení a zápisu řetězců z/do souborů (i na standardní vstup a výstup) je tedy nutná konverze
#### Nastavení charsetu při čtení a zápisu z/do textových souborů
- Konverzi provádějí třídy pro práci se soubory, jejichž název končí ```…Reader/Writer```
- Jsou určeny pro práci se znaky
- Charset vstupního/výstupního souboru lze popsat instancí třídy ```Charset```
- Metoda třídy ```Charset.forName(kódování)```
- Vrátí instanci třídy ```Charset``` reprezentující daný charset na základě jeho jména
- Lze použít kanonické jméno i aliasy
- Metody z třídy ```Files``` pro čtení a zápis z/do souboru umožňují zadat charset souboru jako instanci třídy ```Charset```
- Metoda ```Files.readAllLines(soubor, charset)```
- Metoda ```Files.newBufferedReader(soubor, charset)```
- Metoda ```Files.newBufferedWriter(soubor, charset)```
#### Správné zobrazení češtiny v konzoli Windows
- Od verze Javy 1.8 se čeština v konzoli Windows někdy zobrazuje správně, někdy však stále chybně
- Správně se zobrazují literály s diakritickými znaky zapsané přímo ve zdrojovém kódu
- Špatně se zobrazují řetězce načtené ze standardního vstupu a vypsané na standardní výstup
- Do verze Javy 1.7 včetně se zobrazovaly špatně i literály zapsané přímo ve zdrojovém kódu
- Problém je způsoben rozdílným standardním charsetem Windows (windows-1250) a konzole (implicitně IBM852 lze změnit)
- Tyto charsety nejsou totožné (liší se v některých znacích)
- Java tak očekává na standardním vstupu a výstupu charset windows-1250, ale v konzoli je IBM852
- Korektní zobrazení v Java programech v konzoli Windows lze zařídit nastavením odpovídajícího charsetu pro standardní vstup a standardní výstup
- Pro standardní vstup stačí nastavit charset v konstruktoru třídy Scanner
- Zadává se pouze název charsetu jako řetězec
- Standardní výstup funguje od Javy 1.8 korektně
- POZOR!
- Problémy se týkají pouze konzole (příkazové řádky) Windows
- Konzole IDE nástrojů (např. Eclipse) přebírají charset Windows (jsou to grafická okna) a čeština v nich funguje správně