# 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ř. ``````, `````` 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ě