Útmutató a karakterkódoláshoz

1. Áttekintés

Ebben az oktatóanyagban megvitatjuk a karakterkódolás alapjait és annak kezelését a Java-ban.

2. A karakterkódolás fontossága

Gyakran több nyelvhez tartozó szövegekkel kell megküzdenünk, különféle írási szkriptekkel, például latin vagy arab. Minden nyelv minden karakterét valahogy egy és nulla halmazhoz kell hozzárendelni. Valójában csoda, hogy a számítógépek az összes nyelvünket helyesen képesek feldolgozni.

Ennek megfelelő elvégzéséhez gondolkodnunk kell a karakterkódoláson. Ennek elmulasztása gyakran adatvesztéshez és akár biztonsági résekhez is vezethet.

Ennek jobb megértése érdekében definiáljunk egy módszert egy szöveg dekódolására Java-ban:

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

Vegye figyelembe, hogy az itt betáplált beviteli szöveg az alapértelmezett platformkódolást használja.

Ha ezt a módszert futtatjuk bemenet mint „A homlokzati minta egy szoftver tervezési minta.” és kódolás mint „US-ASCII”, akkor kiadja:

A homlokzati minta egy szoftver tervezési minta.

Nos, nem pontosan arra számítottunk.

Mi romlhatott el? Ezt megpróbáljuk megérteni és kijavítani a bemutató további részében.

3. Alapismeretek

Mielőtt azonban mélyebbre ásnánk, nézzük át gyorsan három kifejezést: kódolás, karakterkészletek, és kódpont.

3.1. Kódolás

A számítógépek csak a bináris reprezentációkat képesek megérteni 1 és 0. Bármi más feldolgozása valamilyen leképezést igényel a valós szövegtől a bináris reprezentációig. Ezt a feltérképezést ismerjük karakterkódolás vagy egyszerűen csak úgy kódolás.

Például üzenetünk első betűje, a T, az US-ASCII-ben kódol a „01010100” értékre.

3.2. Karakterkészletek

A karakterek hozzárendelése bináris ábrázolásukhoz az általuk tartalmazott karakterek tekintetében nagymértékben eltérhet. A leképezésben szereplő karakterek száma a gyakorlati használatban szereplő néhány karaktertől csupán néhányig változhat. A leképezési definícióban szereplő karakterkészletet hivatalosan a-nak hívják karakterkészlet.

Például az ASCII 128 karakterből álló karakterkészlettel rendelkezik.

3.3. Code Point

A kódpont egy absztrakció, amely elválasztja a karaktert a tényleges kódolásától. A kódpont egész szám hivatkozás egy adott karakterre.

Magát az egész számot egyszerű decimális vagy alternatív bázisokkal ábrázolhatjuk, mint például hexadecimális vagy oktális. Alternatív alapokat használunk a nagy számok hivatkozásának megkönnyítése érdekében.

Például az üzenetünk első betűje, a T, az Unicode-ban „U + 0054” (vagy 84-es tizedesjeggyel) kódponttal rendelkezik.

4. A kódolási sémák megértése

A karakterkódolás a kódolt karakterek számától függően különböző formákat ölthet.

A kódolt karakterek száma közvetlen kapcsolatban áll az egyes ábrázolások hosszával, amelyet általában a bájtok számaként mérnek. Több karakter kódolása lényegében hosszabb bináris ábrázolások szükségességét jelenti.

Menjünk át a gyakorlatban néhány népszerű kódolási sémán.

4.1. Egy bájtos kódolás

Az egyik legkorábbi kódolási séma, az úgynevezett ASCII (American Standard Code for Information Exchange) egyetlen bájtos kódolási sémát használ. Ez lényegében azt jelenti az ASCII minden karakterét hét bites bináris számokkal ábrázolják. Ez még mindig egy bitet hagy szabadon minden bájtban!

Az ASCII 128 karakteres halmaza angol ábécét tartalmaz kis- és nagybetűkkel, számjegyekkel, valamint néhány speciális és vezérlő karakterrel.

Definiáljunk egy egyszerű módszert a Java-ban egy karakter bináris ábrázolásának megjelenítésére egy adott kódolási séma alatt:

String convertToBinary (karakterlánc bemenet, karakterlánc kódolás) dobja az UnsupportedEncodingException {byte [] encoded_input = Charset.forName (kódolás) .encode (input) .array (); return 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) .replace (" "," 0 ")) .collect (Collectors.joining (" ")); }

Most a „T” karakter kódpontja 84 az US-ASCII-ben (az ASCII-t a Java-ban US-ASCII-nek nevezik).

És ha hasznossági módszerünket használjuk, láthatjuk a bináris ábrázolását:

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

Amint arra számítottunk, ez egy hétbites bináris reprezentáció a „T” karakter számára.

Az eredeti ASCII minden bájt legjelentősebb bitjét kihasználatlanul hagyta. Ugyanakkor az ASCII elég sok karaktert hagyott képviselet nélkül, különösen a nem angol nyelvek esetében.

Ez arra törekedett, hogy kihasználja ezt a fel nem használt bitet, és további 128 karaktert tartalmazzon.

Az ASCII kódolási sémának több változata volt javasolt és elfogadott az idő múlásával. Ezeket lazán „ASCII kiterjesztéseknek” nevezték.

Számos ASCII kiterjesztés sikeres volt, de nyilvánvalóan ez nem volt elég jó a szélesebb körű átvételhez, mivel sok karakter még mindig nem volt képviselve.

Az egyik legnépszerűbb ASCII kiterjesztés az ISO-8859-1 volt, más néven „ISO Latin 1” néven.

4.2. Több bájtos kódolás

Ahogy nőtt az igény az egyre több karakter befogadására, az egybájtos kódolási sémák, például az ASCII nem voltak fenntarthatóak.

Ez több bájtos kódolási sémákat eredményezett, amelyek sokkal jobb kapacitással rendelkeznek, bár a megnövekedett helyigény árán.

A BIG5 és a SHIFT-JIS példa erre több bájtos karakterkódolási sémák, amelyek egy és két bájtot kezdtek használni a szélesebb karakterkészletek ábrázolására. Ezek többségét abból a célból hozták létre, hogy a kínai és hasonló szkripteket képviseljék, amelyekben lényegesen nagyobb a karakterek száma.

Most hívjuk meg a módszert convertToBinary val vel bemenet mint „‘ ”, kínai karakter, és kódolás mint „Big5”:

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

A fenti kimenet azt mutatja, hogy a Big5 kódolás két bájtot használ a „語” karakter képviseletére.

A karakterkódolások átfogó listáját az álneveikkel együtt a Nemzetközi Számhivatal tartja fenn.

5. Unicode

Nem nehéz megérteni, hogy bár a kódolás fontos, a dekódolás ugyanolyan létfontosságú az ábrázolások értelmezéséhez. Ez a gyakorlatban csak akkor lehetséges, ha konzisztens vagy kompatibilis kódolási sémát széles körben alkalmaznak.

A különállóan kidolgozott és a helyi földrajzi területeken alkalmazott különféle kódolási sémák kezdtek kihívást jelenteni.

Ez a kihívás adott okot az Unicode nevű szinguláris kódolási szabvány, amely képes a világ minden lehetséges karakterére. Ide tartoznak a használt karakterek, és még azok is, amelyek nem működnek!

Nos, ehhez több bájtra van szükség az egyes karakterek tárolásához? Őszintén szólva igen, de az Unicode-nak van egy ötletes megoldása.

Az Unicode standardként meghatározza a világ minden lehetséges karakterének kódpontját. Az Unicode-ban a „T” karakter kódpontja decimálisban 84. Ezt általában „U + 0054” néven emlegetjük az Unicode-ban, ami nem más, mint az U +, amelyet a hexadecimális szám követ.

Hexadecimális értéket használunk a kódpontok alapjául az Unicode-ban, mivel 1 114 112 pont van, ami elég nagy szám ahhoz, hogy kényelmesen, tízes számokkal kommunikálhassunk!

Hogy ezeket a kódpontokat bitekké kódolják, az az egyedi kódolási sémákra van bízva az Unicode-on belül. Ezeknek a kódolási sémáknak egy részét az alábbiakban ismertetjük.

5.1. UTF-32

Az UTF-32 az egy kódolási séma az Unicode számára, amely négy bájtot használ minden kódpont képviseletére az Unicode határozta meg. Nyilvánvaló, hogy helytelen a négy bájt használata minden karakterhez.

Lássuk, hogyan ábrázolják az UTF-32-ben egy olyan egyszerű karaktert, mint a „T”. A módszert fogjuk használni convertToBinary korábban bemutatták:

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

A fenti kimenet négy bájt használatát mutatja a „T” karakter ábrázolására, ahol az első három bájt csak elpazarolt hely.

5.2. UTF-8

Az UTF-8 az egy másik kódolási séma az Unicode számára, amely változó hosszúságú bájtokat használ a kódoláshoz. Míg általában egyetlen bájtot használ a karakterek kódolásához, szükség esetén nagyobb számú bájtot is használhat, így helyet takaríthat meg.

Hívjuk újra a módszert convertToBinary bemenetként „T” és kódolással „UTF-8”:

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

A kimenet pontosan hasonló az egyetlen bájtot használó ASCII-hoz. Valójában az UTF-8 teljesen visszafelé kompatibilis az ASCII-vel.

Hívjuk újra a módszert convertToBinary bemenetként „語” és kódolással „UTF-8”:

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

Amint itt láthatjuk, az UTF-8 három bájtot használ a „語” karakter képviseletére. Ez az úgynevezett változó szélességű kódolás.

Az UTF-8 a helyhatékonysága miatt a leggyakoribb kódolás az interneten.

6. Támogatás kódolása Java-ban

A Java sokféle kódolást és azok egymás közötti átalakítását támogatja. Osztály Charset meghatároz egy sor szabványos kódolást, amelyet a Java platform minden megvalósításának támogatnia kell.

Ide tartozik az US-ASCII, az ISO-8859-1, az UTF-8 és az UTF-16, hogy csak néhányat említsünk. A Java adott megvalósítása opcionálisan támogathat további kódolásokat.

Van néhány finomság abban, ahogy a Java felvesz egy karakterkészletet, amellyel együtt dolgozhat. Nézzük át őket részletesebben.

6.1. Alapértelmezett karakterkészlet

A Java platform nagymértékben függ az úgynevezett tulajdonságtól az alapértelmezett karakterkészlet. A Java virtuális gép (JVM) meghatározza az alapértelmezett karakterkészletet az indítás során.

Ez a területi beállításoktól és az alapul szolgáló operációs rendszer karakterkészletétől függ, amelyen a JVM fut. Például MacOS rendszeren az alapértelmezett karakterkészlet az UTF-8.

Nézzük meg, hogyan határozhatjuk meg az alapértelmezett karakterkészletet:

Charset.defaultCharset (). DisplayName ();

Ha ezt a kódrészletet egy Windows gépen futtatjuk, akkor a kapott kimenetet:

windows-1252

Most a „windows-1252” a Windows platform alapértelmezett karakterkészlete angolul, amely ebben az esetben meghatározta a Windows rendszeren futó JVM alapértelmezett karakterkészletét.

6.2. Ki használja az alapértelmezett karakterkészletet?

Számos Java API használja az alapértelmezett karakterkészletet, amelyet a JVM határoz meg. Hogy csak néhányat említsünk:

  • InputStreamReader és FileReader
  • OutputStreamWriter és FileWriter
  • Formázó és Scanner
  • URLEncoder és URLDecoder

Tehát ez azt jelenti, hogy ha a példát a karakterkészlet megadása nélkül futtatnánk:

új BufferedReader (új InputStreamReader (új ByteArrayInputStream (input.getBytes ()))). readLine ();

akkor az alapértelmezett karakterkészletet használná annak dekódolásához.

És számos olyan API létezik, amelyek alapértelmezés szerint ugyanazt a választást választják.

Az alapértelmezett karakterkészlet tehát olyan fontosságot ölt, amelyet nem hagyhatunk figyelmen kívül.

6.3. Problémák az alapértelmezett karakterkészlettel

Mint láttuk, a Java alapértelmezett karakterkészlete dinamikusan kerül meghatározásra, amikor a JVM elindul. Ezáltal a platform kevésbé megbízható vagy hibára hajlamos, ha különböző operációs rendszereken használják.

Például, ha futunk

új BufferedReader (új InputStreamReader (új ByteArrayInputStream (input.getBytes ()))). readLine ();

macOS-on az UTF-8-at fogja használni.

Ha ugyanazt a kódrészletet próbáljuk ki a Windows rendszeren, akkor a Windows-1252 segítségével fogja dekódolni ugyanazt a szöveget.

Vagy képzelje el, hogy írjon egy fájlt egy macOS-ra, majd olvassa el ugyanazt a fájlt a Windows rendszeren.

Nem nehéz megérteni, hogy a különböző kódolási sémák miatt ez adatvesztéshez vagy korrupcióhoz vezethet.

6.4. Felülírhatjuk az alapértelmezett karakterkészletet?

Az alapértelmezett karakterkészlet meghatározása a Java-ban két rendszer tulajdonsághoz vezet:

  • fájl.kódolás: A rendszer tulajdonság értéke az alapértelmezett karakterkészlet neve
  • sun.jnu.kódolás: Ennek a rendszertulajdonságnak az értéke a fájlkészletek kódolásakor / dekódolásakor használt karakterkészlet neve

Most intuitív, hogy parancssori argumentumokkal felülírja ezeket a rendszer tulajdonságait:

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

Fontos azonban megjegyezni, hogy ezek a tulajdonságok csak olvashatók a Java-ban. A fenti használatuk nincs jelen a dokumentációban. Ezen rendszer tulajdonságainak felülírása nem biztos, hogy kívánatos vagy kiszámítható viselkedést mutat.

Ennélfogva, kerülnünk kell a Java alapértelmezett karakterkészletének felülírását.

6.5. Miért nem oldja meg ezt a Java?

Van egy Java Enhancement javaslat (JEP), amely előírja az „UTF-8” használatát alapértelmezett karakterkészletként a Java-ban, ahelyett, hogy a területi és az operációs rendszer karakterkészletére alapozná.

Ez a JEP jelenleg tervezetben van, és amikor (remélhetőleg!) Átmegy, megoldja a korábban tárgyalt kérdések nagy részét.

Vegye figyelembe, hogy az újabb API-k, mint például az java.nio.file.Files ne használja az alapértelmezett karakterkészletet. Az ezen API-kban szereplő módszerek karakterfolyamokat olvasnak vagy írnak, az alapértelmezett karakterkészlet helyett a karakterkészlet UTF-8-ként szerepel.

6.6. A probléma megoldása programjainkban

Normálisan kellene válassza a karakterkészlet megadását, amikor a szöveggel foglalkozik, ahelyett, hogy az alapértelmezett beállításokra támaszkodna. Kifejezetten deklarálhatjuk azt a kódolást, amelyet használni akarunk azokban az osztályokban, amelyek a karakter-bájt konverziókkal foglalkoznak.

Szerencsére a példánk már meghatározza a karakterkészletet. Csak ki kell választanunk a megfelelőt, és a többit a Java-nak kell hagynunk.

Mostanra észre kell vennünk, hogy az ékezetes karakterek, mint például a ç, nem szerepelnek az ASCII kódoló sémában, ezért szükségünk van egy kódolásra, amely tartalmazza őket. Talán UTF-8?

Próbáljuk ki, most futtatjuk a módszert decodeText ugyanazzal a bemenettel, de kódolással, mint az „UTF-8”:

A homlokzati minta szoftver-tervezési minta.

Bingó! Láthatjuk azt a kimenetet, amelyet most reméltünk.

Itt állítottuk be azt a kódolást, amely szerintünk a legjobban megfelel az igényünknek a InputStreamReader. Ez általában a legbiztonságosabb módszer a karakterek és bájtkonverziók kezelésére a Java-ban.

Hasonlóképpen, OutputStreamWriter és sok más API támogatja a kódoló séma beállítását a konstruktorukon keresztül.

6.7. MalformedInputException

Amikor dekódolunk egy bájtsorozatot, vannak olyan esetek, amelyekben ez nem legális az adott számára Charset, különben nem egy legális tizenhat bites Unicode. Más szavakkal, az adott bájtsorozatnak nincs leképezése a megadottban Charset.

Három előre definiált stratégia létezik (vagy CodingErrorAction), ha a bemeneti szekvencia hibásan alakította a bemenetet:

  • FIGYELMEN KÍVÜL HAGYNI figyelmen kívül hagyja a hibás karaktereket és folytatja a kódolási műveletet
  • KICSERÉLÉS lecseréli a kimeneti puffer hibás karaktereit és folytatja a kódolási műveletet
  • JELENTÉS dobni fog a MalformedInputException

Az alapértelmezett malformedInputAction a A CharsetDecoder a JELENTÉS, és az alapértelmezett malformedInputAction az alapértelmezett dekóder InputStreamReader van KICSERÉLÉS.

Definiáljunk egy dekódoló függvényt, amely egy megadottat fogad Charset, a CodingErrorAction típus és egy dekódolandó karakterlánc:

String decodeText (String input, Charset charset, CodingErrorAction codingErrorAction) dobja az IOException {CharsetDecoder charsetDecoder = charset.newDecoder (); charsetDecoder.onMalformedInput (codingErrorAction); return new BufferedReader (new InputStreamReader (new ByteArrayInputStream (input.getBytes ()), charsetDecoder)). readLine (); }

Tehát, ha dekódoljuk: „A homlokzati minta egy szoftver tervezési minta.” val vel US_ASCII, az egyes stratégiák kimenete eltérő lenne. Először is használjuk CodingErrorAction.IGNORE amely kihagyja az illegális karaktereket:

Assertions.assertEquals ("A homlokzati minta egy szoftver tervezési minta.", CharacterEncodingExamples.decodeText ("A homlokzati minta egy szoftver tervezési minta.", StandardCharsets.US_ASCII, CodingErrorAction.IGNORE));

A második teszthez használjuk CodingErrorAction.REPLACE amely az illegális karakterek helyett:

Assertions.assertEquals ("A fa ade minta egy szoftver tervezési minta.", CharacterEncodingExamples.decodeText ("A homlokzati minta egy szoftver tervezési minta.", StandardCharsets.US_ASCII, CodingErrorAction.REPLACE);

A harmadik teszthez használjuk CodingErrorAction.REPORT ami dobáshoz vezet MalformedInputException:

Assertions.assertThrows (MalformedInputException.class, () -> CharacterEncodingExamples.decodeText ("A homlokzati minta egy szoftver tervezési minta.", StandardCharsets.US_ASCII, CodingErrorAction.REPORT));

7. Egyéb helyek, ahol a kódolás fontos

A programozás során nem csak a karakterkódolást kell figyelembe vennünk. A szövegek sok más helyen véglegesen elromolhatnak.

A A problémák leggyakoribb oka ezekben az esetekben a szöveg konvertálása egyik kódolási sémáról a másikra, ezáltal esetlegesen adatvesztést okoz.

Gyorsan nézzünk át néhány olyan helyet, ahol problémák merülhetnek fel a szöveg kódolása vagy dekódolása során.

7.1. Szövegszerkesztők

Az esetek többségében a szövegszerkesztőből származnak a szövegek. Számos olyan szövegszerkesztő található népszerű választásban, mint a vi, a Jegyzettömb és az MS Word. Ezen szövegszerkesztők többsége lehetővé teszi számunkra a kódolási séma kiválasztását. Ezért mindig meg kell győződnünk arról, hogy azok megfelelnek-e az általunk kezelt szövegnek.

7.2. Fájlrendszer

Miután létrehoztunk szövegeket egy szerkesztőben, azokat valamilyen fájlrendszerben kell tárolnunk. A fájlrendszer az operációs rendszertől függ, amelyen fut. A legtöbb operációs rendszer magában foglalja a több kódolási séma támogatását. Mégis előfordulhatnak olyan esetek, amikor a kódolásos átalakítás adatvesztéshez vezet.

7.3. Hálózat

Ha a hálózaton keresztül olyan protokollok segítségével továbbítják a szövegeket, mint például a File Transfer Protocol (FTP), akkor a karakterkódolások közötti átalakítás is jár. Az Unicode-ban kódolt fájlok esetében a legbiztonságosabb binárisként átvinni, hogy minimalizálja az átalakítás veszteségének kockázatát. A szöveg hálózaton keresztül történő továbbítása azonban az egyik leggyakoribb oka az adatok sérülésének.

7.4. Adatbázisok

A legtöbb népszerű adatbázis, például az Oracle és a MySQL támogatja a karakterkódolási séma kiválasztását az adatbázisok telepítésekor vagy létrehozásakor. Ezt az adatbázisban tárolni kívánt szövegeknek megfelelően kell megválasztanunk. Ez az egyik leggyakoribb hely, ahol a szöveges adatok sérülése a kódolási konverziók miatt következik be.

7.5. Böngészők

Végül a legtöbb webalkalmazásban szövegeket hozunk létre, és különböző rétegeken keresztül továbbítjuk őket azzal a szándékkal, hogy azokat egy felhasználói felületen, például egy böngészőben láthassuk. Itt is elengedhetetlen, hogy a megfelelő karakterkódolást válasszuk, amely képes megfelelően megjeleníteni a karaktereket. A legnépszerűbb böngészők, például a Chrome, az Edge lehetővé teszik a karakterkódolás kiválasztását a beállításaikon keresztül.

8. Következtetés

Ebben a cikkben megvitattuk, hogy a kódolás hogyan lehet kérdés a programozás során.

Tovább megvitattuk az alapokat, beleértve a kódolást és a karakterkészleteket. Sőt, különböző kódolási sémákat és azok felhasználási módjait éltük át.

Emellett felvettünk egy példát a Java-ban a helytelen karakterkódolás használatára, és láttuk, hogyan lehet ezt rendbe hozni. Végül megvitattunk néhány más, a karakterkódolással kapcsolatos általános forgatókönyvet.

Mint mindig, a példák kódja elérhető a GitHub oldalon.