# 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ě

