283 lines
19 KiB
Markdown
283 lines
19 KiB
Markdown
# 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ě
|
||
|