Vejledning til tegnkodning

1. Oversigt

I denne vejledning diskuterer vi det grundlæggende i tegnkodning, og hvordan vi håndterer det i Java.

2. Betydningen af ​​tegnkodning

Vi er ofte nødt til at beskæftige os med tekster, der hører til flere sprog med forskellige skriftskrifter som latin eller arabisk. Hvert tegn på hvert sprog skal på en eller anden måde kortlægges til et sæt af nuller. Det er virkelig et under, at computere kan behandle alle vores sprog korrekt.

For at gøre dette ordentligt, vi er nødt til at tænke på karakterkodning. Hvis du ikke gør det, kan det ofte føre til datatab og endda sikkerhedssårbarheder.

For at forstå dette bedre, lad os definere en metode til at afkode en tekst i Java:

String decodeText (String input, String encoding) kaster IOException {return new BufferedReader (new InputStreamReader (new ByteArrayInputStream (input.getBytes ()), Charset.forName (encoding))) .readLine (); }

Bemærk, at den inputtekst, vi fodrer her, bruger standardplatformkodning.

Hvis vi kører denne metode med input som "Facademønsteret er et softwaredesignmønster." og indkodning som “US-ASCII”, det vil output:

Fade-mønsteret er et softwaredesignmønster.

Nå, ikke nøjagtigt hvad vi forventede.

Hvad kunne være gået galt? Vi prøver at forstå og rette dette i resten af ​​denne vejledning.

3. Grundlæggende

Inden vi graver dybere, skal vi dog gennemgå tre termer: indkodning, tegnog kode punkt.

3.1. Indkodning

Computere kan kun forstå binære repræsentationer som 1 og 0. Behandling af andet kræver en eller anden form for kortlægning fra den virkelige verdens tekst til dens binære repræsentation. Denne kortlægning er, hvad vi kender som tegnkodning eller bare som indkodning.

For eksempel det første bogstav i vores meddelelse, “T”, i US-ASCII koder til “01010100”.

3.2. Tegnsæt

Kortlægningen af ​​tegn til deres binære repræsentationer kan variere meget med hensyn til de tegn, de inkluderer. Antallet af tegn, der er inkluderet i en kortlægning, kan variere fra kun få til alle tegn i praktisk brug. Sættet med tegn, der er inkluderet i en kortlægningsdefinition, kaldes formelt a tegnsæt.

For eksempel har ASCII et tegnsæt på 128 tegn.

3.3. Kodepunkt

Et kodepunkt er en abstraktion, der adskiller et tegn fra dets faktiske kodning. EN kode punkt er et heltal henvisning til et bestemt tegn.

Vi kan repræsentere selve heltallet i almindelig decimal eller alternative baser som hexadecimal eller oktal. Vi bruger alternative baser for at lette henvisningen til et stort antal.

For eksempel har det første bogstav i vores meddelelse, T, i Unicode et kodepunkt "U + 0054" (eller 84 i decimal).

4. Forståelse af kodningsordninger

En tegnkodning kan tage forskellige former afhængigt af antallet af tegn, den koder.

Antallet af kodede tegn har et direkte forhold til længden af ​​hver repræsentation, som typisk måles som antallet af byte. At have flere tegn til at kode betyder i det væsentlige at have brug for længerevarende binære repræsentationer.

Lad os gennemgå nogle af de populære kodningsordninger i praksis i dag.

4.1. Enkeltbyte-kodning

En af de tidligste kodningsordninger, kaldet ASCII (American Standard Code for Information Exchange), bruger en enkelt byte-kodningsplan. Dette betyder i det væsentlige det hvert tegn i ASCII er repræsenteret med syv-bit binære tal. Dette efterlader stadig en smule fri i hver byte!

ASCIIs 128-tegnsæt dækker engelske alfabeter i små og store bogstaver, cifre og nogle specielle og kontroltegn.

Lad os definere en enkel metode i Java til at vise den binære repræsentation for et tegn under et bestemt kodningsskema:

String convertToBinary (strenginput, strengkodning) kaster UnsupportedEncodingException {byte [] encoded_input = Charset.forName (kodning). Encode (input) .array (); returner IntStream.range (0, encoded_input.length) .map (i -> encoded_input [i]) .mapToObj (e -> Integer.toBinaryString (e ^ 255)) .map (e -> String.format ("% 1 $ "+ Byte.SIZE +" s ", e) .placering (" "," 0 ")) .collect (Collectors.joining (" ")); }

Nu har karakteren 'T' et kodepunkt på 84 i US-ASCII (ASCII kaldes US-ASCII i Java).

Og hvis vi bruger vores hjælpemetode, kan vi se dens binære repræsentation:

assertEquals (convertToBinary ("T", "US-ASCII"), "01010100");

Dette er, som vi forventede, en syv-bit binær repræsentation for tegnet 'T'.

Den originale ASCII efterlod den mest betydningsfulde bit af hver byte ubrugt. Samtidig havde ASCII efterladt en hel del tegn ikke repræsenteret, især for ikke-engelske sprog.

Dette førte til et forsøg på at udnytte den ubrugte bit og inkludere yderligere 128 tegn.

Der var adskillige variationer af ASCII-kodningsordningen, der blev foreslået og vedtaget over tid. Disse blev løst kaldt “ASCII-udvidelser”.

Mange af ASCII-udvidelserne havde forskellige succesniveauer, men det var naturligvis ikke godt nok til bredere adoption, da mange tegn stadig ikke var repræsenteret.

En af de mere populære ASCII-udvidelser var ISO-8859-1, også kaldet “ISO Latin 1”.

4.2. Multi-byte-kodning

Da behovet for at rumme flere og flere tegn voksede, var enkeltbyte-kodningsordninger som ASCII ikke bæredygtige.

Dette gav anledning til multi-byte-kodningsordninger, som har en meget bedre kapacitet, omend på bekostning af øgede pladsbehov.

BIG5 og SHIFT-JIS er eksempler på multi-byte-tegnkodningsordninger, der begyndte at bruge en såvel som to byte til at repræsentere bredere tegnsæt. De fleste af disse blev skabt til behovet for at repræsentere kinesiske og lignende scripts, der har et betydeligt højere antal tegn.

Lad os nu kalde metoden konverter til binær med input som '語', en kinesisk karakter og indkodning som “Big5”:

assertEquals (convertToBinary ("語", "Big5"), "10111011 01111001");

Outputtet ovenfor viser, at Big5-kodning bruger to bytes til at repræsentere tegnet '語'.

En omfattende liste over tegnkodninger sammen med deres aliasser vedligeholdes af den internationale nummermyndighed.

5. Unicode

Det er ikke svært at forstå, at mens kodning er vigtig, er afkodning lige så vigtig for at give mening om repræsentationerne. Dette er kun muligt i praksis, hvis der anvendes et ensartet eller kompatibelt kodeskema bredt.

Forskellige kodningsordninger udviklet isoleret og praktiseret i lokale geografier begyndte at blive udfordrende.

Denne udfordring gav anledning til en enestående kodningsstandard kaldet Unicode, som har kapacitet til alle mulige karakterer i verden. Dette inkluderer de tegn, der er i brug, og endda dem, der er nedlagte!

Nå, det skal kræve flere byte for at gemme hvert tegn? Ærligt ja, men Unicode har en genial løsning.

Unicode definerer som standard kodepunkter for alle mulige tegn i verden. Kodepunktet for tegnet 'T' i Unicode er 84 i decimal. Vi henviser generelt til dette som “U + 0054” i Unicode, hvilket kun er U + efterfulgt af det hexadecimale tal.

Vi bruger hexadecimal som base for kodepunkter i Unicode, da der er 1.114.112 point, hvilket er et ret stort antal til at kommunikere bekvemt i decimal!

Hvordan disse kodepunkter er kodet i bits, overlades til specifikke kodningsordninger inden for Unicode. Vi vil dække nogle af disse kodningsordninger i underafsnittene nedenfor.

5.1. UTF-32

UTF-32 er et kodningsskema for Unicode, der anvender fire byte til at repræsentere hvert kodepunkt defineret af Unicode. Det er klart, at det er pladseffektivt at bruge fire byte til hvert tegn.

Lad os se, hvordan en simpel karakter som 'T' er repræsenteret i UTF-32. Vi bruger metoden konverter til binær introduceret tidligere:

assertEquals (convertToBinary ("T", "UTF-32"), "00000000 00000000 00000000 01010100");

Outputtet ovenfor viser brugen af ​​fire byte til at repræsentere tegnet 'T', hvor de første tre byte bare er spildt plads.

5.2. UTF-8

UTF-8 er en anden kodningsplan for Unicode, som anvender en variabel længde af bytes, der skal kodes. Mens det bruger en enkelt byte til generelt at kode tegn, kan det bruge et højere antal bytes, hvis det er nødvendigt, hvilket sparer plads.

Lad os igen kalde metoden konverter til binær med input som 'T' og kodning som "UTF-8":

assertEquals (convertToBinary ("T", "UTF-8"), "01010100");

Outputtet ligner nøjagtigt ASCII ved hjælp af kun en enkelt byte. Faktisk er UTF-8 helt bagudkompatibel med ASCII.

Lad os igen kalde metoden konverter til binær med input som '語' og kodning som "UTF-8":

assertEquals (convertToBinary ("語", "UTF-8"), "11101000 10101010 10011110");

Som vi kan se her bruger UTF-8 tre byte til at repræsentere tegnet '語'. Dette er kendt som kodning med variabel bredde.

UTF-8 er på grund af sin pladseffektivitet den mest almindelige kodning, der bruges på internettet.

6. Kodningsstøtte i Java

Java understøtter en bred vifte af kodninger og deres konverteringer til hinanden. Klassen Charset definerer et sæt standardkodninger, som enhver implementering af Java-platform har mandat til at understøtte.

Dette inkluderer US-ASCII, ISO-8859-1, UTF-8 og UTF-16 for at nævne nogle få. En bestemt implementering af Java kan eventuelt understøtte yderligere kodninger.

Der er nogle finesser i den måde, hvorpå Java opfanger et tegnsæt til at arbejde med. Lad os gennemgå dem i flere detaljer.

6.1. Standardtegn

Java-platformen afhænger stærkt af en ejendom, der kaldes standardtegnsættet. Java Virtual Machine (JVM) bestemmer standard-tegnsættet under opstart.

Dette afhænger af lokaliteten og tegnsættet for det underliggende operativsystem, som JVM kører på. For eksempel på MacOS er standard-tegnsættet UTF-8.

Lad os se, hvordan vi kan bestemme standardtegnet:

Charset.defaultCharset (). Displaynavn ();

Hvis vi kører dette kodestykke på en Windows-maskine, får vi det output:

windows-1252

Nu er “windows-1252” standardcharsættet på Windows-platformen på engelsk, som i dette tilfælde har bestemt standardcharsættet til JVM, der kører på Windows.

6.2. Hvem bruger standardtegnsættet?

Mange af Java API'erne bruger standard-charset som bestemt af JVM. For at nævne et par stykker:

  • InputStreamReader og FileReader
  • OutputStreamWriter og FileWriter
  • Formater og Scanner
  • URLEncoder og URLDecoder

Så det betyder, at hvis vi kører vores eksempel uden at specificere tegnsættet:

ny BufferedReader (ny InputStreamReader (ny ByteArrayInputStream (input.getBytes ()))). readLine ();

så ville det bruge standard-tegnsættet til at afkode det.

Og der er flere API'er, der som standard foretager det samme valg.

Standardcharsættet antager derfor en betydning, som vi ikke sikkert kan ignorere.

6.3. Problemer med standardtegnsættet

Som vi har set, at standardcharsættet i Java bestemmes dynamisk, når JVM starter. Dette gør platformen mindre pålidelig eller udsat for fejl, når den bruges på tværs af forskellige operativsystemer.

For eksempel hvis vi løber

ny BufferedReader (ny InputStreamReader (ny ByteArrayInputStream (input.getBytes ()))). readLine ();

på macOS bruger den UTF-8.

Hvis vi prøver det samme uddrag på Windows, bruger det Windows-1252 til at afkode den samme tekst.

Eller forestil dig at skrive en fil på en macOS og derefter læse den samme fil på Windows.

Det er ikke svært at forstå, at dette på grund af forskellige kodningsordninger kan føre til datatab eller korruption.

6.4. Kan vi tilsidesætte standardtegnsættet?

Bestemmelsen af ​​standard-tegnsættet i Java fører til to systemegenskaber:

  • fil.kodning: Værdien af ​​denne systemegenskab er navnet på standardtegnsættet
  • sun.jnu.encoding: Værdien af ​​denne systemegenskab er navnet på det tegn, der bruges til kodning / afkodning af filstier

Nu er det intuitivt at tilsidesætte disse systemegenskaber gennem kommandolinjeargumenter:

-Dfile.encoding = "UTF-8" -Dsun.jnu.encoding = "UTF-8"

Det er dog vigtigt at bemærke, at disse egenskaber er skrivebeskyttet i Java. Deres anvendelse som ovenfor er ikke til stede i dokumentationen. Tilsidesættelse af disse systemegenskaber har muligvis ikke ønsket eller forudsigelig opførsel.

Derfor, vi bør undgå at tilsidesætte standard-tegnsættet i Java.

6.5. Hvorfor løser Java ikke dette?

Der er et Java Enhancement Proposal (JEP), der foreskriver brug af "UTF-8" som standard-charset i Java i stedet for at basere det på locale og operativsystem-charset.

Denne JEP er i en udkaststilstand fra nu af, og når den (forhåbentlig!) Går igennem, løser den de fleste af de problemer, vi diskuterede tidligere.

Bemærk, at de nyere API'er som dem i java.nio.file.Files Brug ikke standardtegnsættet. Metoderne i disse API'er læser eller skriver tegnstrømme med charset som UTF-8 snarere end standard-charset.

6.6. Løsning af dette problem i vores programmer

Vi burde normalt vælg at angive et tegnsæt, når du beskæftiger dig med tekst i stedet for at stole på standardindstillingerne. Vi kan eksplicit erklære den kodning, vi vil bruge i klasser, der beskæftiger sig med tegn-til-byte-konverteringer.

Heldigvis specificerer vores eksempel allerede charset. Vi skal bare vælge den rigtige og lade Java gøre resten.

Vi skal nu være klar over, at tegn med accent som 'ç' ikke er til stede i kodningsskemaet ASCII, og derfor har vi brug for en kodning, der inkluderer dem. Måske UTF-8?

Lad os prøve det, vi kører nu metoden decodeText med samme indgang, men kodning som “UTF-8”:

Facademønsteret er et softwaredesignmønster.

Bingo! Vi kan se det output, vi håbede på at se nu.

Her har vi indstillet den kodning, som vi synes bedst passer til vores behov i konstruktøren af InputStreamReader. Dette er normalt den sikreste metode til at håndtere tegn og bytekonverteringer i Java.

Tilsvarende OutputStreamWriter og mange andre API'er understøtter indstilling af et kodningsskema gennem deres konstruktør.

6.7. MalformedInputException

Når vi afkoder en bytesekvens, findes der tilfælde, hvor det ikke er lovligt for det givne Charset, ellers er det ikke en lovlig seksten-bit Unicode. Med andre ord har den givne bytesekvens ingen kortlægning i det specificerede Charset.

Der er tre foruddefinerede strategier (eller CodingErrorAction) når indgangssekvensen har forkert indgang:

  • IGNORERE ignorerer misdannede tegn og genoptager kodning
  • ERSTATTE erstatter de misdannede tegn i outputbufferen og genoptager kodningen
  • RAPPORT vil kaste en MalformedInputException

Standardindstillingen misformetInputAction til CharsetDecoder er RAPPORT, og standardindstillingen misformetInputAction af standard dekoderen i InputStreamReader er ERSTATTE.

Lad os definere en afkodningsfunktion, der modtager en specificeret Charset, a CodingErrorAction type og en streng, der skal afkodes:

String decodeText (String input, Charset charset, CodingErrorAction codingErrorAction) kaster IOException {CharsetDecoder charsetDecoder = charset.newDecoder (); charsetDecoder.onMalformedInput (codingErrorAction); returner ny BufferedReader (ny InputStreamReader (ny ByteArrayInputStream (input.getBytes ()), charsetDecoder)). readLine (); }

Så hvis vi afkoder "Facademønsteret er et softwaredesignmønster." med US_ASCII, output for hver strategi ville være anderledes. Først bruger vi CodingErrorAction.IGNORE der springer over ulovlige tegn:

Assertions.assertEquals ("Fademønsteret er et softwaredesignmønster.", CharacterEncodingExamples.decodeText ("Facademønsteret er et softwaredesignmønster.", StandardCharsets.US_ASCII, CodingErrorAction.IGNORE));

Til den anden test bruger vi CodingErrorAction.REPLACE der sætter i stedet for de ulovlige tegn:

Assertions.assertEquals ("Fa  ade mønsteret er et software design mønster.", CharacterEncodingExamples.decodeText ("Facade mønsteret er et software design mønster.", StandardCharsets.US_ASCII, CodingErrorAction.REPLACE));

Til den tredje test bruger vi CodingErrorAction.REPORT hvilket fører til at kaste MalformedInputException:

Assertions.assertThrows (MalformedInputException.class, () -> CharacterEncodingExamples.decodeText ("Facademønsteret er et softwaredesignmønster.", StandardCharsets.US_ASCII, CodingErrorAction.REPORT));

7. Andre steder, hvor kodning er vigtig

Vi behøver ikke bare overveje tegnkodning under programmering. Tekster kan gå galt terminalt mange andre steder.

Det mest almindelige årsag til problemer i disse tilfælde er konvertering af tekst fra et kodningsskema til et andetog dermed muligvis indføre datatab.

Lad os hurtigt gennemgå et par steder, hvor vi kan støde på problemer, når vi koder eller afkoder tekst.

7.1. Teksteditorer

I de fleste tilfælde er en teksteditor, hvor teksterne stammer. Der er adskillige teksteditorer i populære valg, herunder vi, Notepad og MS Word. De fleste af disse teksteditorer giver os mulighed for at vælge kodningsskemaet. Derfor bør vi altid sørge for, at de passer til den tekst, vi håndterer.

7.2. Filsystem

Når vi har oprettet tekster i en editor, skal vi gemme dem i et filsystem. Filsystemet afhænger af det operativsystem, det kører på. De fleste operativsystemer har iboende understøttelse af flere kodningsordninger. Der kan dog stadig være tilfælde, hvor en kodningskonvertering fører til datatab.

7.3. Netværk

Tekster, når de overføres over et netværk ved hjælp af en protokol som FTP (File Transfer Protocol), involverer også konvertering mellem tegnkodninger. For alt kodet i Unicode er det sikreste at overføre som binært for at minimere risikoen for tab ved konvertering. Overførsel af tekst over et netværk er dog en af ​​de mindre hyppige årsager til datakorruption.

7.4. Databaser

De fleste af de populære databaser som Oracle og MySQL understøtter valget af tegnkodningsplan ved installation eller oprettelse af databaser. Vi skal vælge dette i overensstemmelse med de tekster, vi forventer at gemme i databasen. Dette er et af de hyppigere steder, hvor korruption af tekstdata sker på grund af kodning af konverteringer.

7.5. Browsere

Endelig opretter vi i de fleste webapplikationer tekster og sender dem gennem forskellige lag med det formål at se dem i en brugergrænseflade som en browser. Også her er det bydende nødvendigt for os at vælge den rigtige tegnkodning, som kan vise tegnene korrekt. De mest populære browsere som Chrome, Edge tillader valg af tegnkodning gennem deres indstillinger.

8. Konklusion

I denne artikel diskuterede vi, hvordan kodning kan være et problem under programmering.

Vi diskuterede yderligere det grundlæggende, herunder kodning og tegn. Desuden gennemgik vi forskellige kodningsordninger og deres anvendelser.

Vi hentede også et eksempel på forkert brug af tegnkodning i Java og så, hvordan vi fik det rigtigt. Endelig diskuterede vi nogle andre almindelige fejlscenarier relateret til tegnkodning.

Som altid er koden til eksemplerne tilgængelig på GitHub.