Programmering

En grundig titt på Javas karaktertype

1.1-versjonen av Java introduserer en rekke klasser for å håndtere tegn. Disse nye klassene skaper en abstraksjon for å konvertere fra en plattformspesifikk forestilling om karakterverdier til Unicode verdier. Denne kolonnen ser på hva som er lagt til, og motivasjonene for å legge til disse karakterklassene.

Type røye

Kanskje den mest misbrukte basetypen i C-språket er typen røye. De røye type misbrukes delvis fordi den er definert til å være 8 bits, og de siste 25 årene har 8 bits også definert den minste udelbare delen av minnet på datamaskiner. Når du kombinerer sistnevnte faktum med det faktum at ASCII-tegnsettet ble definert til å passe i 7 biter, ble røye type er en veldig praktisk "universell" type. Videre, i C, en peker til en variabel av typen røye ble den universelle pekertypen fordi alt som kunne refereres til som en røye kan også refereres til som hvilken som helst annen type ved bruk av støping.

Bruk og misbruk av røye skriv inn C-språket førte til mange inkompatibiliteter mellom kompilatorimplementeringer, så i ANSI-standarden for C ble det gjort to spesifikke endringer: Den universelle pekeren ble omdefinert til å ha en type tomrom, og krevde dermed en eksplisitt erklæring fra programmereren; og den numeriske verdien av tegn ble ansett å være signert, og definerte dermed hvordan de ville bli behandlet når de ble brukt i numeriske beregninger. Så, på midten av 1980-tallet, fant ingeniører og brukere ut at 8 biter ikke var tilstrekkelig til å representere alle tegnene i verden. Dessverre var C på den tiden så forankret at folk ikke var villige til, kanskje ikke i stand til, å endre definisjonen av røye type. Nå kan du blinke frem til 90-tallet, til den tidlige begynnelsen av Java. Et av de mange prinsippene som ble lagt ned i utformingen av Java-språket, var at tegn ville være 16 biter. Dette valget støtter bruken av Unicode, en standard måte å representere mange forskjellige typer tegn på mange forskjellige språk. Dessverre satte den også scenen for en rekke problemer som bare nå blir rettet opp.

Hva er en karakter uansett?

Jeg visste at jeg var i trøbbel da jeg fant meg selv å stille spørsmålet: "Så hva er et tegn? "Vel, et tegn er en bokstav, ikke sant? En haug med bokstaver utgjør et ord, ord danner setninger og så videre. Virkeligheten er imidlertid at forholdet mellom representasjonen av et tegn på en dataskjerm , kalt sitt glyph, til den numeriske verdien som spesifiserer den glyphen, kalt a kodepunkt, er ikke helt grei i det hele tatt.

Jeg anser meg selv heldig som morsmål på engelsk. For det første fordi det var det vanlige språket til et betydelig antall av dem som bidro til utformingen og utviklingen av den moderne digitale datamaskinen; for det andre fordi det har et relativt lite antall tegn. Det er 96 utskrivbare tegn i ASCII-definisjonen som kan brukes til å skrive engelsk. Sammenlign dette med kinesisk, der det er definert over 20 000 tegn og definisjonen er ufullstendig. Fra begynnelsen av Morse- og Baudot-koden har den generelle enkelheten (få tegn, statistisk utseendefrekvens) til det engelske språket gjort det til språkets språk i den digitale tidsalderen. Men ettersom antallet mennesker som kommer inn i den digitale tidsalderen har økt, har antallet ikke-engelskspråklige også økt. Etter hvert som tallene vokste, mottok flere og flere mennesker stadig større aksept for at datamaskiner brukte ASCII og snakket bare engelsk. Dette økte antall "tegn" datamaskiner som trengs for å forstå. Som et resultat måtte antall tegn kodet av datamaskiner dobles.

Antall tilgjengelige tegn ble doblet da den ærverdige 7-biters ASCII-koden ble innlemmet i en 8-biters tegnkoding kalt ISO Latin-1 (eller ISO 8859_1, "ISO" er den internasjonale standardiseringsorganisasjonen). Som du kanskje har samlet under kodingsnavnet, tillot denne standarden representasjon av mange av de latin-avledede språkene som brukes på det europeiske kontinentet. Bare fordi standarden ble opprettet, betydde det imidlertid ikke at den var brukbar. På den tiden hadde mange datamaskiner allerede begynt å bruke de andre 128 "tegnene" som kan være representert med et 8-biters tegn til en viss fordel. De to eksemplene på bruken av disse ekstra tegnene som er igjen, er IBM PC (PC), og den mest populære dataterminalen noensinne, Digital Equipment Corporation VT-100. Sistnevnte lever videre i form av terminalemulatorprogramvare.

Den faktiske dødstidspunktet for 8-biters karakter vil uten tvil bli diskutert i flere tiår, men jeg knytter det til ved introduksjonen av Macintosh-datamaskinen i 1984. Macintosh brakte to veldig revolusjonerende konsepter inn i vanlig databehandling: karakterskrifter som ble lagret i RAM; og WorldScript, som kan brukes til å representere tegn på hvilket som helst språk. Selvfølgelig var dette ganske enkelt en kopi av det Xerox hadde sendt på maskinene for løvetannsklassen i form av Star tekstbehandlingssystem, men Macintosh brakte disse nye tegnsettene og skriftene til et publikum som fremdeles brukte "dumme" terminaler. . En gang startet, kunne ikke bruken av forskjellige skrifter stoppes - det var bare for attraktivt for for mange mennesker. På slutten av 80-tallet kom trykket for å standardisere bruken av alle disse karakterene i høyden med dannelsen av Unicode Consortium, som publiserte sin første spesifikasjon i 1990. Dessverre, i løpet av 80-tallet og til og med på 90-tallet, antall tegnsett multiplisert. Svært få av ingeniørene som opprettet nye tegnkoder på den tiden, anså den begynnende Unicode-standarden som levedyktig, og derfor opprettet de sine egne mappinger av koder til tegn. Så selv om Unicode ikke ble godt akseptert, var forestillingen om at det bare var 128 eller maksimalt 256 tegn tilgjengelig definitivt borte. Etter Macintosh ble støtte for forskjellige skrifttyper en må-ha-funksjon for tekstbehandling. Åtte-biters tegn ble sviktet ut.

Java og Unicode

Jeg kom inn i historien i 1992 da jeg ble med i Oak-gruppen (Java-språket ble kalt Oak da det først ble utviklet) på Sun. Basistypen røye ble definert til å være 16 usignerte biter, den eneste usignerte typen i Java. Begrunnelsen for 16-biters tegnet var at den ville støtte enhver Unicode-tegnrepresentasjon, og dermed gjøre Java egnet for å representere strenger på alle språk som støttes av Unicode. Men å kunne representere strengen og å kunne skrive den ut har alltid vært separate problemer. Gitt at det meste av opplevelsen i Oak-gruppen kom fra Unix-systemer og Unix-avledede systemer, var det mest komfortable tegnsettet igjen ISO Latin-1. I tillegg til gruppens Unix-arv ble Java I / O-systemet i stor grad modellert på Unix-stream-abstraksjonen, der hver I / O-enhet kunne representeres med en strøm med 8-biters byte. Denne kombinasjonen etterlot noe av en feilfunksjon i språket mellom en 8-biters inndataenhet og 16-biters tegn på Java. Således, hvor som helst Java-strenger måtte leses fra eller skrives til en 8-biters strøm, var det en liten bit kode, et hack, for å magisk kartlegge 8-biters tegn i 16-biters unicode.

I 1.0-versjonene av Java Developer Kit (JDK) var inngangshacket i DataInputStream klasse, og utgangshacket var hele PrintStream klasse. (Egentlig var det en inngangsklasse som het TextInputStream i alfa 2-utgivelsen av Java, men den ble erstattet av DataInputStream hack i selve utgivelsen.) Dette fortsetter å forårsake problemer for begynnende Java-programmerere, ettersom de desperat søker etter Java-ekvivalenten til C-funksjonen. getc (). Vurder følgende Java 1.0-program:

importer java.io. *; public class falske {public static void main (String args []) {FileInputStream fis; DataInputStream dis; røye c; prøv {fis = new FileInputStream ("data.txt"); dis = new DataInputStream (fis); mens (sann) {c = dis.readChar (); System.out.print (c); System.out.flush (); hvis (c == '\ n') pause; } fis.close (); } fange (Unntak e) {} System.exit (0); }} 

Ved første øyekast ser det ut til at dette programmet åpner en fil, leser den ett tegn om gangen og går ut når den første nye linjen leses. Men i praksis er det du får søppelproduksjon. Og grunnen til at du får søppel er at readChar leser 16-biters Unicode-tegn og System.out.print skriver ut hva den antar er ISO Latin-1 8-bit tegn. Men hvis du endrer programmet ovenfor for å bruke readLine funksjon av DataInputStream, ser det ut til å fungere fordi koden i readLine leser et format som er definert med forbigående nikk til Unicode-spesifikasjonen som "modifisert UTF-8." (UTF-8 er formatet som Unicode spesifiserer for å representere Unicode-tegn i en 8-biters inngangsstrøm.) Så situasjonen i Java 1.0 er at Java-strenger består av 16-biters Unicode-tegn, men det er bare en kartlegging som kartlegger ISO Latin-1 tegn til Unicode. Heldigvis definerer Unicode kodesiden "0" - det vil si de 256 tegnene hvis øvre 8 biter er null - som tilsvarer nøyaktig ISO Latin-1 settet. Dermed er kartleggingen ganske triviell, og så lenge du bare bruker ISO Latin-1 tegnfiler, vil du ikke ha noen problemer når dataene forlater en fil, blir manipulert av en Java-klasse og deretter omskrevet til en fil .

Det var to problemer med å begrave inndatakonverteringskoden i disse klassene: Ikke alle plattformer lagret sine flerspråklige filer i modifisert UTF-8-format; og absolutt, applikasjonene på disse plattformene forventet ikke nødvendigvis ikke-latinske tegn i denne formen. Derfor var implementeringsstøtten ufullstendig, og det var ingen enkel måte å legge til den nødvendige støtten i en senere utgivelse.

Java 1.1 og Unicode

Java 1.1-utgivelsen introduserte et helt nytt sett med grensesnitt for håndtering av tegn, kalt Lesere og Forfattere. Jeg endret klassen som heter tull ovenfra til en klasse som heter kul. De kul klasse bruker en InputStreamReader klasse for å behandle filen i stedet for DataInputStream klasse. Noter det InputStreamReader er en underklasse av det nye Leser klasse og System.out er nå en PrintWriter objekt, som er en underklasse av Forfatter klasse. Koden for dette eksemplet er vist nedenfor:

importer java.io. *; public class cool {public static void main (String args []) {FileInputStream fis; InputStreamReader irs; røye c; prøv {fis = new FileInputStream ("data.txt"); irs = ny InputStreamReader (fis); System.out.println ("Bruker koding:" + irs.getEncoding ()); while (true) {c = (char) irs.read (); System.out.print (c); System.out.flush (); hvis (c == '\ n') pause; } fis.close (); } fange (Unntak e) {} System.exit (0); }} 

Den primære forskjellen mellom dette eksemplet og den forrige kodelisten er bruken av InputStreamReader klasse i stedet for DataInputStream klasse. En annen måte som dette eksemplet er forskjellig fra forrige, er at det er en ekstra linje som skriver ut kodingen som brukes av InputStreamReader klasse.

Det viktige poenget er at den eksisterende koden, en gang udokumentert (og tilsynelatende ukjent) og innebygd i implementeringen av getChar metoden for DataInputStream klasse, er fjernet (faktisk er bruken avskrevet; den vil bli fjernet i en fremtidig utgivelse). I 1.1-versjonen av Java er mekanismen som utfører konverteringen nå innkapslet i Leser klasse. Denne innkapslingen gir en måte for Java-klassebibliotekene å støtte mange forskjellige eksterne representasjoner av ikke-latinske tegn mens de alltid bruker Unicode internt.

I likhet med den originale I / O-delsystemdesignen er det selvfølgelig symmetriske motstykker til leseklassene som utfører skriving. Klassen OutputStreamWriter kan brukes til å skrive strenger til en utgangsstrøm, klassen BufferedWriter legger til et lag med buffering, og så videre.

Handelsvorter eller reell fremgang?

Det noe høye målet med utformingen av Leser og Forfatterklassene var å temme det som for tiden er en hodge-podge av representasjonsstandarder for den samme informasjonen ved å tilby en standard måte å konvertere frem og tilbake mellom den eldre representasjonen - det være seg Macintosh Greek eller Windows Cyrillic - og Unicode. Så, en Java-klasse som tar for seg strenger trenger ikke å endres når den beveger seg fra plattform til plattform. Dette kan være slutten på historien, bortsett fra at nå når konverteringskoden er innkapslet, oppstår spørsmålet om hva den koden antar.

Mens jeg undersøkte denne kolonnen, ble jeg påminnet om et berømt sitat fra en Xerox-leder (før det var Xerox, da det var Haloid Company) om at kopimaskinen var overflødig fordi det var ganske enkelt for en sekretær å legge et stykke karbonpapir i skrivemaskinen sin og lage en kopi av et dokument mens hun opprettet originalen. Det som er åpenbart i ettertid, er selvfølgelig at kopimaskinen gagner personen som mottar et dokument mye mer enn den som genererer et dokument. JavaSoft har vist en lignende mangel på innsikt i bruken av karakterkoding og dekodingsklasser i utformingen av denne delen av systemet.

$config[zx-auto] not found$config[zx-overlay] not found