ASCII, UNICODE i UTF-8 - Predstavljanje znakova na računarima
Uvod
Everyone in the world should be able to use their own language on phones and computers.
Citat koji ste prethodno pročitali, preuzet je sa zvaničnog sajta konzorcijuma UNICODE i potcrtava osnovnu ideju koja stoji iza pomenutog standarda, koji (baš kao što je navedeno), godinama unazad omogućava korisnicima najrazličitijih računara, operativnih sistema i korisničkih programa, da koriste na svojim uređajima maternji jezik (uz odgovarajuće pismo), ili bilo koji drugi jezik.
Sa jedne strane, reklo bi se da 'nekako jeste red' da početkom treće decenije dvadesetog veka, bude tako kako je navedeno, ali - situacija nije oduvek bila povoljna.
Nekada su "nad mutnom vodom graktale vrane" .....
Uvođenje ASCII standarda
Pre nego što je postalo moguće na računarima koristiti "sve znakove iz bilo kog jezika", * na red su prvo došli znakovi engleskog alfabeta (uz cifre, znakove interpunkcije, znakove osnovnih matematičkih operacija i određeni broj drugih uobičajenijih znakova).
Međutim, bilo je potrebno uspostaviti (pre svega), osnovni sistem koji uopšte omogućava bilo kakvo korišćenje znakova na računarima.
Da bismo vam malčice "zakomplikovali život" (zapravo, da bismo vam pomogli da što bolje razumete tematiku članka :)), pre nego što pređemo na ASCII i UNICODE - osvrnućemo se na osnovni princip zapisivanja znakova na računarima (a nešto kasnije, takođe ćemo se osvrnuti i na vrane). :)
Kako računari (uopšte) mogu razumeti znakove?
U članku u kome smo diskutovali o tome zašto binarni brojevni sistem (a ne dekadni), predstavlja matematičku osnovu računarskih sistema, objasnili smo da računari, ne samo da nisu u stanju da neposredno razumeju dekadni brojevni sistem, već - nisu u stanju da neposredno razumeju brojeve ("ikako").
Iz prethodno navedenog, "nekako sledi", da - od računara koji nisu u stanju da neposredno razumeju (čak ni binarne) brojeve - "nije baš za očekivati" da budu u stanju da neposredno razumeju znakove?!
Međutim (u praktičnom smislu), ukoliko su logička kola u računarima u stanju da prepoznaju dva različita naponska stanja: "nema napona" i "ima napona" (a kao što je poznato, logička kola u računarima jesu sposobna za takvo raspoznavanje), navedena naponska stanja se mogu shvatiti i kao cifre binarnog brojevnog sistema: 0 i 1, a nule i jedinice se dalje mogu nizati tako da tvore binarne brojeve (koji kasnije, 'ako zatreba', mogu biti interpretirani, tako da korisnicima budu prikazani kao dekadni brojevi).
Nizanjem bitova (ili, da budemo precizniji i praktičniji - nizanjem bajtova), takođe se mogu kodirati i znakovi.
Osnovna ideja za kodiranje znakova (makar u okviru jednog računarskog sistema), podrazumeva da se svaki znak povezuje sa unapred izabranom brojčanom vrednošću (uvek istom), a kada se navedena ideja uspešno implementira u okviru jednog računarskog sistema - ostaje "samo" pitanje - kako uskladiti više različitih računarskih sistema.
U prvim godinama razvoja računarske industrije (druga polovina 50-ih godina 20. veka i veći deo 60-ih godina), odnosno, pre uvođenja ASCII standarda, zapis znakova na različitim računarskim sistemima - nije bio usklađen.
Različite, raznolike i udaljene računare, odlikovali su idiosinkratični sistemi za zapis znakova (idiosinkratičan = sebi svojstven), i stoga - reklo bi se krajnje očekivano - nije postojala ni mogućnost razmene tekstualnih poruka.
U navedenom periodu, dok su računari (u kontekstu šire primene), još uvek delovali kao uređaji iz domena naučne fantastike, nemogućnost međusobne komunikacije računara nije predstavljala preveliki problem (recimo da je donekle tako), ali, razmišljalo se i o budućnosti, za koju se još onomad očekivalo da će podrazumevati širu rasprostranjenost računara i veći obim komunikacije između računara, i zaključeno je da takva budućnost neće biti moguća bez (pre svega), opšteprihvaćenog standarda za razmenu tekstualnih poruka između različitih računarskih sistema.
Pojava ASCII standarda i osnovne odrednice
American Standard Code for Information Interchange (skraćeno - ASCII), prvi je široko rasprostranjen i opšteprihvaćen standard za kodiranje znakova u elektronskoj komunikaciji, i kao takav, podržan je na većini računarskih sistema koji su se u međuvremenu pojavili.
Prva specifikacija ASCII standarda objavljena je 1963, međutim, ASCII je svoj krajnji oblik poprimio krajem šezdesetih godina.
U osnovnom tehničkom smislu, ASCII standard podrazumeva da se brojevi u rasponu od 0 do 127 koriste za kodiranje velikih i malih slova engleskog alfabeta, cifara i drugih pažljivo biranih specijalnih znakova.
ASCII tabela
ASCII tabela sadrži 128 znakova (koje možete videti u donjem formularu) i formatirana je tako da prve 32 pozicije zauzimaju 'kontrolni znakovi' (to jest, znakovi koji se ne vide u štampi ili na ekranu, već imaju posebnu funkciju u različitim računarskim sistemima), dok ostale pozicije zauzimaju znakovi koji se mogu videti i predstavljeni su grafičkim simbolima.
Što se tiče kontrolnih znakova koji zauzimaju početne pozicije (i ne vide se u ispisu), izdvojićemo nekoliko uobičajenijih (sa dekadnim kodovima, za lakše snalaženje):
- 8 - Backspace (taster BACKSPACE)
- 13 - Carriage return (taster "ENTER")
- 27 - Escape (taster ESC)
- 32 - Space (razmak na tastaturi)
(Za ostale znakove koje prepoznajete od ranije, možete videti grafičke simbole.)
Pravilnosti u dodeljivanju mesta u tabeli ciframa i slovima
ASCII tabela može delovati donekle nasumično na prvi pogled, međutim, kada su u pitanju pozicije slova i cifara, zapravo postoji prilično jasna ideja vodilja: udesiti tabelu tako da se cifra 1, kao i veliko i malo slovo 'A' - "poklope sa jedinicom".
Da pojasnimo o čemu se radi ....
Kao prvo, jasno je da poziciju #1 u ASCII tabeli (niti bilo koju drugu poziciju), ne mogu zauzimati tri različita znaka istovremeno, vidimo i to da nijedan od tri navedena znaka nije na prvoj poziciji u tabeli i (takođe) - ne deluje na prvi pogled da znakovi '0', 'A' i 'a' imaju previše veze sa kodnim vrednostima 48, 65 i 97 (sa kojima su povezani).
Međutim, ako pažljivije sagledamo binarne vrednosti koje su pripisane ciframa i slovima, možemo zapaziti red i pravilnosti.
Ako posmatramo zapis cifara preko ASCII tabele (uzećemo samo nekoliko prvih cifara za primer) ....
.... može se zapaziti da bitovi na najnižim pozicijama odgovaraju vrednostima cifara (uz uključene bitove na pozicijama 32 i 16, što daje zbir 48):
- cifra
'0'
ima ASCII kod48
, što se može shvatiti kao48 + 0
- cifra
'1'
ima ASCII kod49
, što se može shvatiti kao48 + 1
- cifra
'2'
ima ASCII kod50
, što se može shvatiti kao48 + 2
Ista ideja stoji i iza zapisa velikih slova:
.... gde se takođe može zapaziti da bitovi na najnižim pozicijama odgovaraju pozicijama znakova u engleskom alfabetu ....
- znak
'A'
ima ASCII kod65
, što se može shvatiti kao64 + 1
- znak
'B'
ima ASCII kod66
, što se može shvatiti kao64 + 2
- znak
'C'
ima ASCII kod67
, što se može shvatiti kao64 + 3
Mala slova takođe se kodiraju po sličnom principu:
Bitovi na najnižim pozicijama (ponovo) odgovaraju pozicijama znakova u engleskom alfabetu i (takođe), kodne vrednosti su veće za 32 u odnosu na velika slova:
- znak
'a'
ima ASCII kod97
, što se može shvatiti kao64 + 32 + 1
- znak
'b'
ima ASCII kod98
, što se može shvatiti kao64 + 32 + 2
- znak
'c'
ima ASCII kod99
, što se može shvatiti kao64 + 32 + 3
Poznavanje navedenih pravilnosti, dobro dođe pri pisanju programa.
Manipulacija bitovima i drugi programski kodovi za ispis znakova
Manipulacijom bitova (pod uslovom da smo sigurni da znak jeste "ono što bi trebalo da bude"), velika slova se vrlo lako mogu pretvarati u mala slova, mala slova se mogu pretvarati u velika slova, znakovi koji predstavljaju cifre mogu se pretvarati u odgovarajuće celobrojne vrednosti i sl.
Na primer:
Kad smo već kod programskih kodova, u C-u je (takođe) vrlo jednostavno dobiti ispis kodne vrednosti znaka:
Pomenimo da se navedene operacije mogu izvesti i preko JavaScript-a * (pri čemu ima i dodatnih mogućnosti) ....
.... i (naravno), većina popularnih jezika takođe omogućava slične zahvate.
Proširene ASCII tabele / kodne stranice
ASCII tabela je sedmobitna (u smislu: bilo koji pojedinačni znak iz ASCII tabele, može se definisati preko sedam bita), međutim - u praksi - pojedinačni ASCII znakovi se zapisuju korišćenjem celih bajtova (tj. preko osam bita), i stoga je jasno da u takvom zapisu ima mesta za dodatnih 128 znakova.
U periodu posle početnog 'uhodavanja' sa ASCII standardom, a pogotovo između kraja sedamdesetih i sredine devedesetih godina dvadesetog veka, pojavile su se brojne 'varijacije na temu' ASCII tabele.
U navedenom kontekstu, termin 'kodna stranica' (eng. code page), odnosi se na konkretnu 'proširenu ASCII tabelu' (koja se koristi u određenom računarskom sistemu).
U okviru različitih kodnih stranica, pozicije iznad 127 korišćene su za dodavanje podrške za specijalne znakove opšteg tipa, ili, za definisanje znakova različitih svetskih pisama.
Kodna stranica 437
Uzmimo kao prvi primer (iz razloga nekadašnje velike popularnosti), kodnu stranicu 437, izmenjenu i proširenu ASCII tabelu koja je uvedena sa prvobitnim PC računarima, odnosno sa operativnim sistemom DOS (a korišćena je i u ranim verzijama Windows-a):
Kodna stranica 437 nije smišljena sa ciljem da pruži podršku za znakove iz "ne-anglosaksonskih" pisama, već - da omogući prikaz dodatnih specijalnih znakova, pre svega onih koji su korišćeni za iscrtavanje prozora u okviru tekstualnog okruženja (verujemo da se programeri sa podužim stažom, i dalje sećaju vremena kada su se znakovi sa gornje slike koristili za kreiranje prozora u DOS programima). *
Međutim, u poslednjim godinama pre nego što je standard UNICODE praktično 'istisnuo' kodne stranice iz upotrebe, pojam "kodna stranica" se tipično vezivao za proširene ASCII tabele sa podrškom za različita pisma, i stoga, pre nego što konačno pređemo na UNICODE i formate za kodiranje znakova (pre svega UTF-8, ali i druge), napravićemo kratak pregled nekada popularnih kodnih stranica sa podrškom za ćirilične i latinične znakove. **
Kodna stranica 1251 (ćirilica)
Kodna stranica 1251, odgovara znakovima srpskog ćiriličnog pisma (zarad preglednosti, navešćemo samo nekoliko znakova) ....
Kodna stranica 852 (latinica)
Za latinična pisma iz slovenskih jezika, bila je zadužena kodna stranica 852:
Kodna stranica 737 (grčki alfabet)
Da bismo dodatno ilustrovali (relativnu) * proizvoljnost pri razmeštanju znakova u kodnim stranicama, uzećemo za primer još i Kodnu Stranicu 737, preko koje su definisana grčka slova (nekoliko pozicija koje smo već navodili) ....
Dovoljno je pogledati prethodne primere i doći do zaključka da kodne stranice (bar iz današnje perspektive), deluju pomalo neorganizovano, a jasno je i to da bilo kakva greška u smislu pogrešnog odabira kodne stranice, dovodi do toga da tekst postane praktično nečitljiv (greške su svakako manje izražene u slučajevima kada se pomešaju različite verzije latiničnih pisama, dok, ako pomešamo, na primer, ćirilicu i grčko pismo - nećemo biti "takve sreće").
Naravno, može se iz sadašnje perspektive gledati na kodne stranice na način kako pojedini ljudi posmatraju fosilne ostatke predaka homo sapiensa, ali, evolucija se ne dešava 'preko noći', nekada nije bilo 'mostova i puteva', i trebalo je ipak vremena i truda da se sve izgradi kako dolikuje.
U svakom slučaju, do sada opisana situacija se može rezimirati na sledeći način:
- ASCII tabela nudi razuman raspored znakova i podršku za engleski alfabet, ali - ne podržava znakove ostalih pisama, niti druge (specijalne) znake
- kodne stranice su (u praktičnom smislu), suviše neorganizovane
.... i iz svega se jasno može zaključiti da je bilo potrebno iznaći praktičnije rešenje.
UNICODE
UNICODE je standard za kodiranje znakova u računarskim sistemima, iza koga stoji nekoliko osnovnih ideja:
- glavni cilj je - obuhvatiti sve znakove, iz svih svetskih pisama, odnosno - koliko god je moguće znakova
- svaki znak mora imati jedinstvenu kodnu poziciju (eng. code point) - što je pristup koji je idejno sličan ASCII standardu
- UNICODE specifikacija mora biti potpuno kompatibilna sa ASCII specifikacijom (kodovi znakova iz ASCII tabele odgovaraju kodnim pozicijama iz UNICODE specifikacije)
Konzorcijum UNICODE osnovan je krajem osamdesetih godina dvadesetog veka, osnivači projekta su programeri Džo Beker (Joe Becker), Li kolins (Lee Collins) i Mark Dejvis (Mark Davis), a podršku pružaju brojni saradnici iz različitih računarskih kompanija (pogotovo onih koje su direktno zainteresovane za održavanje standarda u oblasti obrade teksta na računarima).
U početku, specifikacija je obuhvatala nekoliko desetina hiljada znakova (što svakako nije mali broj), ali, već do početka 21. veka prevaziđen je broj od 100 hiljada znakova (što je zahtevalo 'prepravljanje' tehničkih rešenja za enkodiranje znakova).
U kontekstu tehničkih rešenja, od svih odlika UNICODE-a koje smo naveli, posebno se ističe očuvanje kompatibilnosti sa ASCII standardom (pri čemu je doneta racionalna i praktična odluka da se ASCII tabela "ne dira" - već samo proširi), što posebno dolazi do izražaja ako se za enkodiranje UNICODE znakova koristi format UTF-8 (o svemu navedenom ćemo pisati detaljnije u nastavku).
Enkodiranje UNICODE znakova
U okviru standarda UNICODE, znakovi se kodiraju prema dogovoru, tako da svakom znaku odgovara jedinstvena 'kodna pozicija' (eng. code point), čime je otklonjena zbrka koju je (ranije) unosilo korišćenje različitih kodnih stranica:
- prvih 128 pozicija * zauzimaju redom znakovi iz ASCII tabele
- slede zatim uobičajenija pisma evropskih jezika, sa znakovima koji zauzimaju kodne pozicije relativno blizu početnih ASCII pozicija (na primer, ćirilica, latinica, grčki i ruski alfabet i sl)
- više pozicije zauzimaju manje uobičajena (ali i dalje široko rasprostranjena), pisma sa velikim brojem znakova (kinesko, japansko, korejsko, indijsko i sl)
- najviše / najudaljenije pozicije, zauzimaju najrazličitije vrste specijalnih znakova (emotikoni i sl)
Sledeće pitanje je: kako (zapravo) kodirati kodne pozicije?
ASCII znakovi zauzimaju 1 bajt (tačnije, 7 bita; manje od jednog celog bajta), dok, sa UNICODE znakovima (kojih ima mnogo više od 27), to nije i ne može biti slučaj.
Na primer, sledećih nekoliko znakova "različite udaljenosti od ASCII pozicija", shodno svojim kodnim pozicijama ....
.... očigledno zahtevaju više od jednog bajta za zapis (dva, tri, ili četiri).
Kao prvobitno rešenje pojavio se standard UCS-2 (Universal Coded Character Set), koji je podrazumevao kodiranje svakog znaka preko dva bajta.
U poslednjoj deceniji 20. veka, u vreme dok je ukupan broj kodnih pozicija bio manji od 216 (65536), format UCS-2 bio je sasvim adekvatan, ali, nadomak 21. veka - broj kodnih pozicija počeo je da raste ....
UNICODE Transformation Format (UTF-16 - UTF-32 - UTF-8)
Kada je broj kodnih pozicija prevazišao smeštajni kapacitet 16-bitnih promenljivih, uveden je standard UTF-16, koji predstavlja unapređenje specifikacije UCS-2, a potom su uvedeni i standardi UTF-32 i UTF-8.
U nastavku ćemo posvetiti pažnju tehničkim detaljima navedenih metoda za kodiranje znakova, prednostima i nedostacima različitih metoda, kao i oblastima primene.
UTF-16
Format UTF-16 (kao jedan od mogućih formata za kodiranje UNICODE znakova), predstavlja unapređenje formata UCS-2, * uz očuvanje osnovne ideje da se za zapis znakova koriste blokovi od 16 bita.
Početna pretpostavka da će (za)uvek biti dovoljno koristiti 216 znakova za zapis svih pojedinačnih UNICODE znakova, odavno je opovrgnuta (kao što smo već nagovestili), a takođe je jasno i to da se preko jednog bloka od 16 bita, ne mogu kodirati brojevi veći od 216.
Shodno navedenom, jedino praktično rešenje svodi se na uvođenje dodatnog 16-bitnog bloka (po potrebi), uz korišćenje posebnog (i 'ne baš skroz jednostavnog') postupka, na koji ćemo se osvrnuti u nastavku.
Krenimo redom i razmotrimo prvo situacije u kojima nije potrebno uvođenje dodatnih bajtova ....
Kada su u pitanju niže kodne pozicije, zapis znaka preko formata UTF-16 (baš kao i u slučaju starog standarda UCS-2), obavlja se uz korišćenje dva bajta.
Primera radi, znak 'Č' (koji smo već pominjali) - čija je kodna pozicija 268 - može se kodirati na sledeći način:
Za znak kao što je 'Č' (sa kodnom pozicijom koja je veća od 127 i manja od 65536), korišćenje dva bajta je racionalan i optimalan izbor.
Ako se format UTF-16 primeni za kodiranje znaka 'A', čija je kodna pozicija ("i dalje") 65 ....
.... vidimo da je zapis moguć (naravno), ali, takođe vidimo da zapis "ASCII znakova" preko formata UTF-16, praktično dovodi do dupliranja memorijskog zauzeća (reklo bi se - sasvim nepotrebno):
Iako UTF-16 dozvoljava korišćenje dodatne grupe od dva bajta (po potrebi), nije moguće selektivno koristiti samo jedan bajt za znakove sa prvih 128 pozicija (koje, same po sebi, ne zahtevaju dodatne bitove).
Pošto smo se osvrnuli na znakove koji se mogu kodirati preko jednog 16-bitnog bloka, vraćamo se na diskusiju o kodnim pozicijama sa kojima to nije slučaj ....
Zapis kodnih pozicija većih od 65535 - parovi surogata
U okviru UNICODE specifikacije, postoji svojevrsna "rupa" ('bermudski trougao'), između kodnih vrednosti 55296 (0xD800) i 57343 (0xDFFF).
Navedeni raspon ne koristi se za (direktno) kodiranje znakova, već, pojava kodne vrednosti iz navedenog raspona (pri čitanju teksta), sugeriše da je u pitanju znak sa kodnom pozicijom koja je veća ili jednaka 65536, i takav znak se tumači na poseban način.
Sam raspon kodnih vrednosti od 55296 do 57343, podeljen je na dve grupe tzv. "surogata":
- pozicije od 55296 do 56319 zauzimaju viši surogati (eng. high surrogates)
- pozicije od 56320 do 57343 zauzimaju niži surogati (eng. low surrogates)
Oba raspona sadrže po 1024 vrednosti (1010), a prostim nadovezivanjem * dve desetobitne vrednosti, može se dobiti jedna dvadesetobitna vrednost, što omogućava kodiranje znakova sve do kodne pozicije 1048576 (1020 = 1048576).
Kao što smo ranije nagovestili, ceo postupak podrazumeva (naravno), korišćenje dva uzastopna 16-bitna bloka.
Kada program koji obavlja dekodiranje po standardu UTF-16, naiđe na 16-bitni blok iz raspona viših surogata (55296-56319), pojava takvog bloka tumači se kao informacija - koja se tiče toga da je za pravilno dekodiranje znaka potrebno pročitati još jedan 16-bitni blok (za koji se očekuje da mora pripadati rasponu nižih surogata, 56320 - 57343) *
Kodna pozicija se formira spajanjem dve grupe od po deset bitova, koje se 'izvlače' iz dve uzastopne 16-bitne vrednosti iz raspona surogata.
Prvo ćemo prikazati primer kodiranja znaka ("da bismo uopšte imali šta da čitamo"), pri čemu ćemo zapisati kodnu poziciju 128578 (osnovni "smajli").
Za početak, potrebno je oduzeti vrednost 65536 (216), od broja koji predstavlja kodnu poziciju znaka:
Posle oduzimanja, 20-bitni zapis dobijene vrednosti (63042), deli se na dva 10-bitna bloka (kao što smo već nagovestili), posle čega se viših 10 bitova 'utiskuje' u raspon viših surogata, dok se preostalih 10 bitova 'utiskuje' u raspon nižih surogata.
Da bi 10 viših (tj. 'levih') bitova, bilo 'utisnuto' u raspon viših surogata, potrebno je sabrati prikazanu desetobitnu vrednost (111101), sa početnom pozicijom raspona viših surogata (55296, odnosno 0xD800):
Nakon sabiranja, dobijeni rezultat (55357) - zapisuje se u prvi 16-bitni blok:
Nižih (tj. 'desnih') 10 bitova (1001000010), sabira se sa početnom pozicijom raspona nižih surogata (563200, odnosno 0xDC00):
.... a dobijeni rezultat (56898), zapisuje se kao druga kombinacija od dva bajta:
Znak je sada kodiran.
Pri čitanju, potrebno je proći kroz sledeće korake (smatramo da je uspešno prepoznato da dve uzastopne 16-bitne vrednosti pripadaju odgovarajućim rasponima surogata, i prolazi se kroz 'obrnuti postupak'):
- od prve 16-bitne vrednosti oduzima se 0xD800 (u binarnom zapisu, praktično se isključuju svi bitovi, osim onih na 10 najnižih pozicija) *
- od druge 16-bitne vrednosti oduzima se 0xDC00 (i u ovom slučaju praktično ostaju samo bitovi na 10 najnižih pozicija) *
- bitovi u prvoj grupi pomeraju se za deset mesta ulevo, spajaju se sa bitovima iz druge grupe (preko pomoćne 32-bitne promenljive), a potom se rezultat uvećava za 65536 **
Provera
Za razliku od gornjeg primera, u kome smo samo pretpostavili da je redosled 16-bitnih blokova ispravan, u praksi se podaci moraju proveravati.
Provera se obavlja prema sledećim pravilima:
- 16-bitni blok koji ne pripada rasponu surogata, tumači se kao zaseban znak
- pojava 16-bitnog bloka iz raspona viših surogata, podrazumeva čitanje sledećeg bloka (pri čemu se očekuje da "sledeći" blok pripada rasponu nižih surogata (kao što smo ranije naveli))
-
sledeće okolnosti tumače se kao 'signal' da je došlo do greške:
- 16-bitni blok sa vrednošću iz raspona nižih surogata, pojavljuje se pre odgovarajuće vrednosti iz raspona viših surogata
- 16-bitni blok sa vrednošću iz raspona viših surogata, pojavljuje se posle 16-bitnog bloka koji takođe sadrži vrednost iz raspona viših surogata
- pojedinačna vrednost iz raspona viših ili nižih surogata - pojavljuje se samostalno
Prednosti, nedostaci, oblast upotrebe
Da bi UTF-16 (kao način za kodiranje znakova), bio zaista univerzalan, morala bi biti otklonjena dva nedostatka:
- zapis UTF-16 očigledno nije direktno kompatibilan sa ASCII datotekama (direktno čitanje ASCII datoteke, pri čemu bi se znakovi koji inače zauzimaju jedan bajt, čitali u blokovima od po dva bajta, dovelo bi do grešaka)
- konvertovanje ASCII datoteka u UTF-16 datoteke, duplira memorijsko zauzeće
Pomenuti nedostaci čine format UTF-16 nepraktičnim za kodiranje web stranica i trajno čuvanje teksta, ali, UTF-16 se i dalje koristi kao interni format za zapis znakova u operativnim sistemima i programskim jezicima (što su situacije u kojima dodatno memorijsko zauzeće ne predstavlja preveliki problem, pa stoga UTF-16 predstavlja praktičnije rešenje od UTF-8 zapisa i starijih, prevaziđenih rešenja).
UTF-32
Format za kodiranje UTF-32, predstavlja način da se bilo koji znak zapiše neposredno, preko 32 bita - bez 'utiskivanja bitova' ili drugih metoda za 'šifriranje' (nasuprot formatu UTF-16, ili formatu UTF-8, o kome ćemo tek pisati).
Međutim, iako 32-bitni format (232 = 4294967296), nedvosmisleno omogućava da se obuhvati celokupan raspon UNICODE znakova - zapis sam po sebi nije optimalan (u najpraktičnijem smislu, memorijsko zauzeće je preveliko).
Ako kao prvi primer ponovo uzmemo znak '🙂' (popularni smajli), čija kodna pozicija 128578 "prirodno" zahteva 3 bajta za zapis ....
.... jasno je da se i u ovakvom slučaju ("najmanje dramatičnom"), jedan ceo bajt uopšte ne koristi:
Po pitanju "rasipanja prostora", stvari su još ozbiljnije kada se zapisuju uobičajeniji UNICODE znakovi ....
.... u kom slučaju se nepotrebno koriste dva bajta ....
..... i naravno, najviše 'rasipanja' ima u situaciji kada se zapisuju znakovi iz ASCII specifikacije ....
.... u kom slučaju se nepotrebno koriste cela tri bajta:
Format UTF-32 (za razliku od formata UTF-16 i UTF-8), nije previše zastupljen u praksi, ali, povremeno se koristi u određenim programima.
Kao što smo već spominjali, za prenos i čuvanje većih količina teksta, najčešće se koristi prilično elegantno rešenje u vidu formata UTF-8 ....
Format UTF-8 (specifikacija)
Osnovna ideja koja stoji iza formata UTF-8, podrazumeva kodiranje znakova preko blokova od 8 bita, tako da se znakovi iz ASCII tabele zapisuju preko jednog bajta ("i dalje"), dok se za ostale znakove uvode dodatni bajtovi.
Dakle (da dodatno 'istaknemo poentu'): dodatni blokovi se uvode - samo po potrebi, i stoga nema 'nepotrebnog rasipanja'.
Shodno navedenom, jasno je da format UTF-8 omogućava prostornu uštedu, međutim, zapis viših UNICODE pozicija (slično kao kada se koristi UTF-16), relativno je kompleksan i zahteva malo više procesorskog vremena pri čitanju i pisanju znakova (u odnosu na prosto čitanje ASCII znakova).
Sve što smo naveli "deluje lepo", ali, glavno pitanje je - kako se ideje sprovode u delo.
Pravila za formiranje kodnih pozicija uz korišćenje jednog ili dva bajta (sa primerima)
Znakovi iz ASCII tabele ....
.... zapisuju se (u praktičnom smislu), na isti način kako se zapisuju u samoj ASCII tabeli.
Međutim (kao što verovatno naslućujete), glavna informacija u smislu enkodiranja ili dekodiranja znaka, sadržana je u prvom bitu:
- ako je prvi bit 0 - praktično je u pitanju je znak iz ASCII tabele
- ako je prvi bit 1 - u pitanju je znak sa kodnom pozicijom koja je veća od 127 (i takav znak se kodira uz korišćenje dva ili više bajta)
Na gornjoj slici, prvi bit nije uključen, i upravo na taj način je UTF-8 parseru predata informacija o tome da neće biti potrebno čitati dodatne bajtove (zarad dekodiranja datog znaka), što znači da se znak može pročitati odmah - iz preostalih sedam bitova.
Ako je (nasuprot prethodnom slučaju), prvi bit uključen, potrebno je očitati bar dva bajta:
Očitavanje znakova funkcioniše prema sledećim opštim pravilima:
- ukoliko je prvi bit u prvom bajtu 0, kodna pozicija znaka se čita iz prvog bajta (iz preostalih 7 bita)
- ukoliko je prvi bit u prvom bajtu 1, čitaju se i naredni bitovi, sve dok se ne očita 0
- broj uzastopnih bitova sa vrednošću 1, na početku prvog bajta, praktično definiše ukupan broj bajtova koji se koriste za kodiranje znaka
- prva dva bita u preostalim bajtovima, moraju biti redom
1
i0
- preko svih preostalih bitova (u svim bajtovima), koji se posmatraju kao jedinstvena niska bitova, * zapisuje se kodna pozicija
Vraćamo se na dekodiranje znaka 'Č' ....
Kada se na prvoj poziciji (u prvom bajtu), pojavi jedinica, potrebno je očitati i drugi bit, i - ako se na drugoj poziciji takođe pojavi jedinica (a na trećoj poziciji nula) ....
.... to znači da je za formiranje kodne pozicije potrebno pročitati još jedan bajt (ili, malo drugačije, samo još jedan bajt), odnosno, znači da se dati znak kodira preko ukupno dva bajta:
Drugi bajt mora počinjati kombinacijom bitova 10
....
.... a kada se izdvoje preostali bitovi u oba bajta ....
.... dobija se 11 bitova preko kojih je moguće zapisivati znakove sa kodnim pozicijama u rasponu od 128 do 2047 (211 - 1).
Konkretna kombinacija bitova, 100001100
, definiše vrednost 268 (256 + 8 + 4), što predstavlja kodnu poziciju znaka 'Č' (sa kojom smo se već susretali u članku).
Svrha kontrolnih bitova (algoritam za proveru)
Kodiranje drugog bajta (odnosno, ostalih bajtova), kombinacijom bitova 10
na samom početku, može delovati pomalo "nepotrebno", ali, u pitanju je kontrolni mehanizam koji je uveden pre svega zarad situacija u kojima se tekst ne prenosi u obliku datoteka, već, znak-po-znak (streaming), i takav kontrolni mehanizam pruža informaciju o potencijalnim greškama koje mogu nastati u prenosu.
Za početak, uzmimo kao primer situaciju u kojoj se očitava znak koji je kodiran preko dva bajta (pri čemu se na prvoj poziciji uredno pojavio bajt koji počinje sa 110
):
- ako drugi bajt počinje nulom, to je signal da znak nije pravilno enkodiran, što znači da prethodni bajt treba odbaciti (i formirati kodnu vrednost korišćenjem samo drugog bajta - koji se u navedenim okolnostima prepoznaje kao samostalni "ASCII" znak)
- ako drugi bajt počinje sa
11
, to je takođe signal da prethodni bajt treba odbaciti, ali - u ovom slučaju - drugi bajt (koji počinje sa11
), tretira se kao prvi bajt u višebajtnom kodiranju UNICODE znaka *
Prethodno navedenim pravilima - koja važe i u situacijama kada se određeni znak kodira preko tri ili više bajta - dodaćemo još i sledeća pravila:
- ukoliko se (pri čitanju znaka), bajt koji počinje sa
10
pojavi pre bajta koji počinje sa110
(ili1110
i sl) - u pitanju je greška. - ukoliko je znak kodiran preko više od dva bajta (tri, četiri i sl), i pri tom bilo koji od bajtova posle prvog, ne počinje sa
10
- takođe je u pitanju greška **
Primeri formiranja kodnih pozicija uz korišćenje tri ili više bajtova
Za formiranje kodnih pozicija koje prevazilaze vrednost 2047, koristi se (bar) tri bajta, po prethodno opisanom principu: u prvom bajtu je zapisano koliko (ukupno) bajtova se koristi za enkodiranje, tj. dekodiranje znaka, a ostali počinju sa 10
.
Za enkodiranje znaka uz korišćenje 3 bajta, raspoloživo je 16 bitova
, i stoga je maksimalna kodna pozicija koja se može zapisati uz korišćenje 3 bajta: 65535
.
Za formiranje kodnih pozicija koje prevazilaze 65535, potrebno je koristiti (bar) četiri bajta.
U situaciji kada se koristi 4 bajta za enkodiranje znaka, na raspolaganju je 21 bit
, i stoga je maksimalna kodna pozicija koja se može zapisati preko četiri bajta: 2097151
.
Teoretski, moguće je koristiti i pet, pa čak i šest bajtova (što možete isprobati u mini-aplikaciji na kraju ovog članka), ali, budući da se preko četiri bajta može zapisati bilo koji znak čija je kodna pozicija između 65536 i 2097151, pri čemu navedena gornja granica osetno premašuje broj znakova koji su trenutno prisutni u UNICODE specifikaciji, formatima koji koriste više od četiri bajta - ne moramo pridavati preveliki značaj (u praktičnom smislu).
Primer čitanja poruke koja je enkodirana po formatu UTF-8
Da bismo još bolje utvrdili princip kodiranja znakova preko formata UTF-8, razmotrićemo primer.
Tekstualna datoteka sastoji se iz sledećih bajtova: 196 140 97 197 161 97
....
.... i potrebno je pročitati sadržaj datoteke.
Početak prvog bajta ....
.... sugeriše da je za 'dešifrovanje' znaka potrebno pročitati još jedan bajt:
Preko 11 bitova (u prva dva bajta), definisana je vrednost 268 - što predstavlja znak 'Č':
Sledeći bajt, počinje nulom ....
.... što znači da se odmah može pročitati preostalih 7 bitova, u kojima je zapisana vrednost 97, što predstavlja znak 'a':
Početak četvrtog bajta (slično kao i početak prvog bajta) ....
.... sugeriše da je za dekodiranje znaka potrebno pročitati ukupno dva bajta (odnosno - još jedan bajt) ....
.... a preko 'osenčenih' 11 bitova ....
.... definisana je vrednost 353, odnosno znak 'š'.
Šesti (poslednji) bajt, počinje nulom ....
.... što znači da se znak odmah može dekodirati preko preostalih 7 bitova (ponovo je u pitanju vrednost 97, to jest, znak 'a'):
Sadržaj datoteke je (reč): "Čaša".
Formulari: UTF-8 enkoder / dekoder
Kao što smo ranije nagovestili, formulare u narednim odeljcima možete iskoristiti za isprobavanje 'teorije' sa kojom smo se upoznali kroz članak.
Enkodiranje
Enkoder uzima vrednost iz numeričkog ulaznog polja, vraća detaljan prikaz strukture bitova enkodiranog znaka, brojčane vrednosti bajtova (u dekadnom obliku), i ispisuje znak.
Dekodiranje
Dekoder uzima (iz ulaznog polja), niz dekadnih brojeva u rasponu od 0 do 255 (koji su razdvojeni razmacima i praktično predstavljaju "bajtove"), a ukoliko niz "bajtova" predstavlja poruku koja je pravilno kodirana po standardu UTF-8, dekodirana poruka će biti ispisana ispod ulaznog polja.
Zaključak / kratka analiza
Iako smo se u članku "podosta napričali" o metodama za enkodiranje i dekodiranje znakova, tema za razgovor (zapravo) ima još ....
Najviše prostora posvetili smo standardu UTF-8 i implicirali smo da navedeni standard (uglavnom) predstavlja optimalno rešenje, međutim, osvrnućemo se i na prilično očigledne nedostatke UTF-8 zapisa:
- u pojedinim slučajevima, format UTF-8 je manje ekonomičan od formata UTF-16 (primer: znakovi kineskog pisma mogu se zapisati preko jednog bloka od 2 bajta ako se koristi format UTF-16; ako se koristi format UTF-8, potrebno je koristiti 3 bajta)
- procedure za enkodiranje i dekodiranja UTF-8 znakova, relativno su komplikovane za implementaciju, i stoga je i enkodiranje (tj. dekodiranje) UTF-8 znakova, nešto sporije
Ako se izraz "win-win" (iz engleskog jezika), odnosi na pogodbe u kojima svaka strana dobija (ili na situacije u kojima sve izlazi na dobro), onda se celokupna situacija sa metodama UTF enkodiranja, može shvatiti (pomalo slikovito), kao svojevrsna "lose-lose" situacija (jer nijedna od opcija nije optimalna sama po sebi).
Sa druge strane, nedostaci ni izdaleka nisu "zabrinjavajući", i stoga je potrebno biti praktičan:
- tamo gde je optimalno koristiti UTF-16 - koristi(će) se UTF-16 (isto važi i za UTF-32, format koji, kao što smo već naveli, ipak nije toliko često u upotrebi)
- programski jezici godinama unazad sadrže funkcije za enkodiranje i dekodiranje po standardima UTF-8, UTF-16 i UTF-32, što znači da programeri koji ne žele time da se bave - ne moraju time da se bave
- savremeni računari su duži niz godina dovoljno brzi da omoguće da razlika u brzini čitanja ASCII datoteka (sa jedne strane), i UTF-8 datoteka (sa druge) - bude praktično zanemarljiva
Takođe, mogli bismo spomenuti da metode enkodiranja UTF-16, UTF-32 i UTF-8, nisu jedine postojeće (ali, svakako jesu najuobičajenije, pogotovo UTF-8), pa - ako vas tematika predstavljanja znakova na računarima dodatno zanima - alternativne metode za kodiranje znakova, mogu biti dobra ideja za dalje istraživanje ....