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

284 lines
19 KiB
Markdown
Raw Permalink Normal View History

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