Programování

Podrobný pohled na typ znaků Java

Verze Java verze 1.1 zavádí řadu tříd pro práci se znaky. Tyto nové třídy vytvářejí abstrakci pro převod z konceptu hodnot znaků specifických pro platformu Unicode hodnoty. Tento sloupec se zabývá tím, co bylo přidáno, a motivací pro přidání těchto tříd postav.

Typ char

Snad nejvíce zneužívaným základním typem v jazyce C je typ char. The char typ je částečně zneužit, protože je definován jako 8 bitů a za posledních 25 let definoval 8 bitů také nejmenší nedělitelný kus paměti v počítačích. Když zkombinujete druhou skutečnost se skutečností, že znaková sada ASCII byla definována tak, aby se vešla do 7 bitů, znak char typ je velmi vhodný „univerzální“ typ. Dále v C ukazatel na proměnnou typu char se stal univerzálním typem ukazatele, protože vše, na co lze odkazovat jako char lze také použít jako jakýkoli jiný typ pomocí odlévání.

Používání a zneužívání char typ v jazyce C vedl k mnoha nekompatibilitám mezi implementacemi kompilátoru, takže ve standardu ANSI pro C byly provedeny dvě konkrétní změny: Byl změněn univerzální ukazatel tak, aby měl typ prázdnoty, což vyžadovalo explicitní prohlášení programátora; a číselná hodnota znaků byla považována za podepsanou, což definovalo, jak by se s nimi zacházelo při použití v numerických výpočtech. V polovině 80. let pak inženýři a uživatelé zjistili, že 8 bitů nestačí na to, aby představovaly všechny postavy na světě. Bohužel, v té době už bylo C tak zakořeněné, že lidé nebyli ochotni, možná ani neschopní, změnit definici char typ. Nyní blikejte dopředu do 90. let, do raných počátků Javy. Jedním z mnoha principů stanovených v designu jazyka Java bylo, že znaky budou 16 bitů. Tato volba podporuje použití Unicode, standardní způsob reprezentace mnoha různých druhů postav v mnoha různých jazycích. Naneštěstí to také připravilo půdu pro řadu problémů, které se opravují teprve nyní.

Co je to vlastně postava?

Věděl jsem, že mám potíže, když jsem zjistil, že kladu otázku: „No a co je postava? "No, postava je písmeno, že? Spousta písmen tvoří slovo, slova tvoří věty atd. Realitou však je, že vztah mezi reprezentací postavy na obrazovce počítače , volal jeho glyf, na číselnou hodnotu, která určuje daný glyf, nazývaný a kódový bod, není vůbec jednoduché.

Považuji za štěstí, že jsem rodilým mluvčím anglického jazyka. Zaprvé proto, že se jednalo o společný jazyk významného počtu těch, kteří přispěli k návrhu a vývoji moderního digitálního počítače; za druhé, protože má relativně malý počet glyfů. V definici ASCII je 96 tisknutelných znaků, které lze použít k psaní angličtiny. Porovnejte to s čínštinou, kde je definováno více než 20 000 glyfů a tato definice je neúplná. Od raných počátků v Morseově a Baudotově kódu se díky celkové jednoduchosti (několik glyfů, statistická četnost výskytu) anglického jazyka stala lingua-franca digitálního věku. Ale jak se zvýšil počet lidí vstupujících do digitálního věku, zvýšil se i počet rodilých mluvčích angličtiny. Jak počet rostl, stále více a více lidí bylo stále méně ochotných akceptovat, že počítače používají ASCII a mluví pouze anglicky. To značně zvýšilo počet „postav“ počítačů potřebných k pochopení. Výsledkem bylo, že počet glyfů kódovaných počítači se musel zdvojnásobit.

Počet dostupných znaků se zdvojnásobil, když byl ctihodný 7bitový kód ASCII začleněn do 8bitového kódování znaků zvaného ISO Latin-1 (nebo ISO 8859_1, přičemž „ISO“ je Mezinárodní organizace pro normalizaci). Jak jste se mohli domnívat podle názvu kódování, tento standard umožňoval reprezentaci mnoha jazyků odvozených z latiny používaných na evropském kontinentu. Jen proto, že byl standard vytvořen, neznamenalo, že je použitelný. V té době už spousta počítačů začala používat dalších 128 „znaků“, které by mohly být s výhodou představovány 8bitovým znakem. Dva dochované příklady použití těchto dalších znaků jsou osobní počítač IBM (PC) a nejpopulárnější počítačový terminál vůbec, Digital Equipment Corporation VT-100. Ten žije dál ve formě softwaru terminálového emulátoru.

Skutečná doba smrti 8bitové postavy bude bezpochyby diskutována po celá desetiletí, ale věřím tomu při zavedení počítače Macintosh v roce 1984. Macintosh přinesl do mainstreamových výpočtů dva velmi revoluční koncepty: znaková písma, která byla uložena v RAM; a WorldScript, které lze použít k reprezentaci znaků v jakémkoli jazyce. Samozřejmě to byla jen kopie toho, co Xerox dodával na svých strojích třídy Pampeliška v podobě systému zpracování textu Star, ale Macintosh přinesl tyto nové znakové sady a písma publiku, které stále používalo „hloupé“ terminály . Jakmile bylo možné začít používat různá písma, nebylo možné je zastavit - bylo to příliš lákavé pro příliš mnoho lidí. Koncem 80. let tlak na standardizaci používání všech těchto znaků vyvrcholil vytvořením konsorcia Unicode, které vydalo svou první specifikaci v roce 1990. Bohužel v 80. letech a dokonce i v 90. letech vynásobený počet znakových sad. Jen velmi málo inženýrů, kteří v té době vytvářeli nové kódy znaků, považovalo rodící se standard Unicode za životaschopný, a proto vytvořili vlastní mapování kódů na glyfy. Takže i když Unicode nebyl dobře přijat, představa, že bylo k dispozici pouze 128 nebo maximálně 256 znaků, byla definitivně pryč. Po systému Macintosh se podpora různých písem stala nezbytnou funkcí pro zpracování textu. Osm bitových postav mizelo v zánik.

Java a Unicode

Do příběhu jsem vstoupil v roce 1992, kdy jsem se v Sunu připojil ke skupině Oak (jazyk Java se při prvním vývoji jmenoval Oak). Základní typ char byl definován jako 16 nepodepsaných bitů, jediný nepodepsaný typ v Javě. Důvodem 16bitového znaku bylo, že bude podporovat jakoukoli reprezentaci znaků Unicode, takže Java bude vhodná pro reprezentaci řetězců v jakémkoli jazyce podporovaném Unicode. Ale být schopen reprezentovat řetězec a být schopen jej vytisknout byly vždy samostatné problémy. Vzhledem k tomu, že většina zkušeností ve skupině Oak pocházela ze systémů Unix a systémů odvozených od Unixu, nejpohodlnější znakovou sadou byla opět ISO Latin-1. Také s unixovým dědictvím skupiny byl systém I / O Java z velké části modelován na abstrakci proudu Unix, přičemž každé I / O zařízení mohlo být reprezentováno proudem 8bitových bajtů. Tato kombinace zanechala v jazyce něco jako porucha mezi 8bitovým vstupním zařízením a 16bitovými znaky Java. Tedy kdekoli bylo nutné číst řetězce Java nebo zapisovat do 8bitového proudu, byl tam malý kousek kódu, hack, který magicky mapoval 8bitové znaky do 16bitového unicode.

Ve verzích 1.0 Java Developer Kit (JDK) byl vstupní hack v DataInputStream třídy a výstupní hack byl celý PrintStream třída. (Ve skutečnosti byla pojmenována vstupní třída TextInputStream ve verzi alfa 2 Java, ale byla nahrazena DataInputStream hack ve skutečném vydání.) To nadále způsobuje problémy začínajícím programátorům Java, protože zoufale hledají ekvivalent Java funkce C getc (). Zvažte následující program Java 1.0:

importovat java.io. *; public class falošný {public static void main (String args []) {FileInputStream fis; DataInputStream dis; char c; try {fis = new FileInputStream ("data.txt"); dis = new DataInputStream (fis); while (true) {c = dis.readChar (); System.out.print (c); System.out.flush (); if (c == '\ n') break; } fis.close (); } catch (Výjimka e) {} System.exit (0); }} 

Na první pohled se zdá, že tento program otevře soubor, přečte jej po jednom znaku a ukončí se při přečtení prvního nového řádku. V praxi však získáte nevyžádaný výstup. A důvod, proč se ti to líbí, je ten readChar čte 16bitové znaky Unicode a System.out.print vytiskne to, co předpokládá, jsou 8bitové znaky ISO Latin-1. Pokud však výše uvedený program změníte tak, aby používal readLine funkce DataInputStream, zdá se, že funguje, protože kód v readLine přečte formát, který je definován předáním kývnutí na specifikaci Unicode jako „upravený UTF-8“. (UTF-8 je formát, který Unicode určuje pro reprezentaci znaků Unicode v 8bitovém vstupním proudu.) Situace v prostředí Java 1.0 je tedy taková, že řetězce Java jsou složeny ze 16bitových znaků Unicode, ale existuje pouze jedno mapování, které mapuje Znaky ISO Latin-1 do Unicode. Naštěstí Unicode definuje kódovou stránku „0“ - tedy 256 znaků, jejichž horních 8 bitů je nulových - aby přesně odpovídaly sadě ISO Latin-1. Mapování je tedy docela triviální a pokud používáte pouze soubory znaků ISO Latin-1, nebudete mít žádné problémy, když data opustí soubor, bude manipulována třídou Java a poté přepsána do souboru .

S pohřbením vstupního převáděcího kódu do těchto tříd došlo ke dvěma problémům: Ne všechny platformy ukládaly své vícejazyčné soubory v upraveném formátu UTF-8; a určitě aplikace na těchto platformách nečekaly nutně nelatinské znaky v této podobě. Podpora implementace byla proto neúplná a nebylo možné snadno přidat potřebnou podporu v pozdější verzi.

Java 1.1 a Unicode

Vydání Java 1.1 představilo zcela novou sadu rozhraní pro práci se znaky, tzv Čtenáři a Spisovatelé. Upravil jsem pojmenovanou třídu falešný shora do třídy s názvem chladný. The chladný třída používá InputStreamReader třídy, aby soubor zpracoval, nikoli DataInputStream třída. Všimněte si, že InputStreamReader je podtřídou nového Čtenář třída a System.out je nyní PrintWriter objekt, který je podtřídou Spisovatel třída. Kód tohoto příkladu je uveden níže:

importovat java.io. *; public class cool {public static void main (String args []) {FileInputStream fis; Irky InputStreamReader; char c; try {fis = new FileInputStream ("data.txt"); irs = nový InputStreamReader (fis); System.out.println ("Použití kódování:" + irs.getEncoding ()); while (true) {c = (char) irs.read (); System.out.print (c); System.out.flush (); if (c == '\ n') break; } fis.close (); } catch (Výjimka e) {} System.exit (0); }} 

Primárním rozdílem mezi tímto příkladem a předchozím výpisem kódu je použití InputStreamReader třída spíše než DataInputStream třída. Dalším způsobem, kterým se tento příklad liší od předchozího, je to, že existuje další řádek, který vytiskne kódování použité InputStreamReader třída.

Důležitým bodem je, že existující kód, jednou nezdokumentovaný (a zdánlivě nepoznatelný) a vložený do implementace getChar metoda DataInputStream třída, byla odstraněna (ve skutečnosti je její použití zastaralé; bude odstraněno v budoucím vydání). Ve verzi Java verze 1.1 je nyní mechanismus provádějící převod zapouzdřen v Čtenář třída. Toto zapouzdření poskytuje způsob, jak knihovny tříd Java podporují mnoho různých externích reprezentací jiných než latinských znaků, zatímco vždy interně používají Unicode.

Samozřejmě, stejně jako původní návrh subsystému I / O, existují symetrické protějšky tříd čtení, které provádějí zápis. Třída OutputStreamWriter lze použít k zápisu řetězců do výstupního proudu, třídy BufferedWriter přidá vrstvu vyrovnávací paměti atd.

Obchodování s bradavicemi nebo skutečný pokrok?

Poněkud vznešený cíl designu Čtenář a Spisovateltříd bylo zkrotit to, co je v současné době nástrojem standardů reprezentace pro stejné informace, poskytnutím standardního způsobu převodu tam a zpět mezi zastoupeným zastoupením - ať už je to Macintosh Greek nebo Windows Cyrillic - a Unicode. Třída Java, která se zabývá řetězci, se tedy při přechodu z platformy na platformu nemusí měnit. To by mohl být konec příběhu, až na to, že nyní, když je kód převodu zapouzdřen, vyvstává otázka, co tento kód předpokládá.

Při zkoumání tohoto sloupce mi připomněl slavný citát výkonného ředitele společnosti Xerox (předtím to byl Xerox, když to byla společnost Haloid Company) o tom, že je kopírka nadbytečná, protože pro sekretářku bylo docela snadné vložit kousek uhlíkového papíru do svůj psací stroj a při vytváření originálu vytvořila kopii dokumentu. Při zpětném pohledu je samozřejmě zřejmé, že fotokopírovací stroj je pro osobu přijímající dokument mnohem výhodnější než pro osobu generující dokument. JavaSoft ukázal podobný nedostatek vhledu do používání tříd kódování a dekódování znaků při navrhování této části systému.