Java konverzioni tok: most između Java bajt-tokova i znak-tokova
Konverzioni tok može da umota bajt-tok u znak-tok, ili znak-tok u bajt-tok. Ovakva konverzija se najčešće koristi pri obradi tekstualnih podataka, npr. čitanje tekstualnih fajlova ili prenos podataka sa mreže u aplikaciju.
Postoje dve glavne vrste konverzionih tokova: InputStreamReader i OutputStreamWriter.
InputStreamReader pretvara ulazni bajt-tok u ulazni znak-tok, dok OutputStreamWriter pretvara izlazni bajt-tok u izlazni znak-tok. Za konverziju između bajt-tokova i znak-tokova koriste zadati skup znakova. Najčešće se koriste UTF-8, GBK, ISO-8859-1 i slični.

01. Kodiranje i dekodiranje
U računarima se podaci obično skladište i prenose u binarnom obliku.
- Kodiranje je pretvaranje sirovih podataka (tekst, slika, video, audio itd.) u binarni oblik.
- Dekodiranje je pretvaranje binarnih podataka nazad u sirove podatke — inverzan proces.
Postoji mnogo uobičajenih načina kodiranja i dekodiranja; evo nekoliko primera:
- ASCII kodiranje i dekodiranje: u računarima se ASCII kod često koristi za predstavljanje znakova — slova na tastaturi, cifre, simboli itd. Na primer, slovo A ima ASCII kod 65, a znak + ASCII kod 43.
- Unicode kodiranje i dekodiranje: Unicode je skup znakova koji podržava različite jezike i pisma. U računarima Unicode može koristiti UTF-8, UTF-16 i druge načine kodiranja da znakove pretvori u binarne podatke radi skladištenja i prenosa.
- Base64 kodiranje i dekodiranje: Base64 je način kodiranja kojim se binarni podaci pretvaraju u ASCII znakove. Trojku bajtova pretvara u 4 ASCII znaka radi upotrebe pri mrežnom prenosu. Na primer, kada se string "Hello, world!" kodira u Base64, rezultat je "SGVsbG8sIHdvcmxkIQ==".
- Kodiranje i dekodiranje slika: u obradi slika se često koriste JPEG, PNG, GIF i drugi načini kodiranja da se slika pretvori u binarne podatke radi skladištenja i prenosa. Pri dekodiranju se binarni podaci pretvaraju nazad u sliku radi prikaza ili dalje obrade.
- Kodiranje i dekodiranje videa: u obradi videa se često koriste H.264, AVC, MPEG-4 i drugi načini kodiranja da se video pretvori u binarne podatke radi skladištenja i prenosa. Pri dekodiranju se binarni podaci pretvaraju nazad u video radi puštanja ili dalje obrade.
Jednostavnije rečeno:
- Kodiranje: znakovi (razumljivi) --> bajtovi (nerazumljivi)
- Dekodiranje: bajtovi (nerazumljivi) --> znakovi (razumljivi)
Da to pokažem kodom:
String str = "Chenmo Wang Er";
String charsetName = "UTF-8";
// Kodiranje
byte[] bytes = str.getBytes(Charset.forName(charsetName));
System.out.println("Kodiranje: " + bytes);
// Dekodiranje
String decodedStr = new String(bytes, Charset.forName(charsetName));
System.out.println("Dekodiranje: " + decodedStr);U ovom primeru prvo definišemo stringovnu promenljivu str i ime skupa znakova charsetName. Zatim, pomoću metode Charset.forName() dobijamo Charset objekat za zadati skup znakova. Potom, metodom getBytes() stringa kodiramo string u niz bajtova u željenom skupu znakova. Na kraju, pomoću new String() dekodiramo niz bajtova nazad u string.
Važno je napomenuti da se tokom kodiranja i dekodiranja mora koristiti isti skup znakova, kako bi podaci bili ispravno pretvoreni.
02. Skup znakova
Charset: skup znakova — kolekcija znakova u kojoj svaki znak ima jedinstvenu kodnu vrednost, tzv. kodnu tačku (code point).
Najpoznatiji skupovi znakova su ASCII, Unicode i GBK; Unicode skup znakova obuhvata više načina kodiranja, poput UTF-8 i UTF-16.

ASCII skup znakova
ASCII (American Standard Code for Information Interchange) skup znakova jedan je od najranijih; sadrži 128 znakova, uključujući kontrolne znakove, cifre, engleska slova i neke interpunkcijske znakove. Svaki znak u ASCII skupu ima jedinstven 7-bitni binarni kod (sastavljen od 0 i 1), koji se može zapisati kao decimalni ili heksadecimalni broj.
ASCII način kodiranja je fiksne dužine — svaki znak se predstavlja sa 7 binarnih bitova. ASCII može da predstavi samo engleska slova, cifre i mali broj simbola, a ne i slova te znakove drugih jezika, zbog čega mu je globalna upotreba prilično ograničena.
Unicode skup znakova
Unicode obuhvata gotovo sve znakove na svetu i služi za predstavljanje ljudskih jezika, simbola, emodžija i raznih drugih informacija. Svaki znak u Unicode skupu ima jedinstvenu kodnu tačku (code point) koja označava njegov položaj u skupu i može se zapisati kao heksadecimalni broj.
Da bi se znakovi iz Unicode skupa skladištili i prenosili u računaru, potreban je način kodiranja. UTF-8, UTF-16 i UTF-32 su načini kodiranja Unicode skupa znakova; oni znakove pretvaraju u nizove bajtova radi skladištenja i prenosa. Razlikuju se po dužini u bajtovima.
- UTF-8 je promenljive dužine: za ASCII znakove (raspon kodnih tačaka
0x00~0x7F) koristi jedan bajt, a za ostale Unicode znakove dva, tri ili četiri bajta. UTF-8 je široko prihvaćen na internetu i u računarstvu jer podatke kompresuje efikasno, pa je pogodan i za mrežni prenos i za skladištenje. - UTF-16 je fiksne dužine: za znakove u osnovnoj višejezičnoj ravni (Basic Multilingual Plane — raspon kodnih tačaka u Unicode skupu koji obuhvata većinu svakodnevnih znakova, preko 65.000 kodnih tačaka ukupno), čiji je raspon
0x0000~0xFFFF, koristi dva bajta, a za ostale Unicode znakove četiri bajta. - UTF-32 je fiksne dužine: za sve Unicode znakove koristi četiri bajta.
GBK skup znakova
GBK obuhvata sve znakove iz GB2312 skupa i proširuje ih mnogobrojnim drugim kineskim (han) znakovima i simbolima — ukupno 21.913 znakova. GBK koristi dvobajtno kodiranje; svaki kineski znak zauzima 2 bajta, pri čemu i viši i niži bajt koriste po 8 bitova, pa GBK ima 2^16=65536 mogućih kodova, od kojih većina služi za predstavljanje kineskih znakova.
GBK je promenljive dužine: za ASCII znakove (raspon kodnih tačaka 0x00 do 0x7F) koristi jedan bajt, a za ostale znakove dva bajta. U GBK kodiranju svaki bajt može uzeti bilo koju vrednost između 0x81 i 0xFE, te može predstaviti 2^15=32768 znakova. Da bi se izbegao sukob sa ASCII-jem, prvi bajt GBK koda uzima sve vrednosti od 0x81 do 0xFE osim 0x7F, a drugi bajt uzima sve vrednosti iz raspona 0x40 do 0x7E i 0x80 do 0xFE — ukupno 94 vrednosti.
Puno ime GB2312 je „Osnovni skup kineskih znakova za razmenu informacija“, poznat i kao „nacionalni standard“. Koristi dvobajtno kodiranje; svaki kineski znak zauzima 2 bajta, pri čemu i viši i niži bajt koriste po 8 bitova, pa GB2312 ima 2^16=65536 mogućih kodova, od kojih većina služi za predstavljanje kineskih znakova. U GB2312 svaki bajt može uzeti bilo koju vrednost između 0xA1 i 0xF7, što daje 126 znakova.
GB2312 je prilično jednostavan skup znakova — obuhvata samo često korišćene kineske znakove i simbole, pa za ređe i arhaične znakove nije dovoljan. Danas ga postepeno zamenjuju GBK, GB18030 i slični skupovi.
GB18030 je najnoviji kineski kodni standard. Sadrži 70.244 kineska znaka i koristi višebajtno kodiranje — svaki znak može imati 1, 2 ili 4 bajta. Podržava pisma nacionalnih manjina iz Kine, kao i tradicionalne kineske znakove te japanske i korejske (han) znakove.
03. Nečitljivi znakovi (mojibake)
Kada se fajl čita ili upisuje različitim načinima kodiranja, javlja se problem nečitljivih znakova; pogledajmo primer.
String s = "Chenmo Wang Er!";
try {
// Čuva string u fajl koristeći GBK kodiranje
OutputStreamWriter out = new OutputStreamWriter(
new FileOutputStream("logs/test_utf8.txt"), "GBK");
out.write(s);
out.close();
FileReader fileReader = new FileReader("logs/test_utf8.txt");
int read;
while ((read = fileReader.read()) != -1) {
System.out.print((char)read);
}
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}U gornjem primeru prvo definišemo string koji sadrži kineske znakove, zatim ga sačuvamo u fajl koristeći GBK kodiranje, a potom fajl čitamo podrazumevanim kodiranjem (UTF-8) i prikazujemo sadržaj. Tada nastupa problem nečitljivih znakova: ispis je „??Ĭ??????“.
Razlog je što GBK-kodirani znakovi u fajlu ne mogu ispravno da se protumače pri UTF-8 dekodiranju, što dovodi do nečitljivog ispisa.
Kako onda rešiti taj problem?
Tu na scenu stupa glavni junak današnjeg članka — konverzioni tok.
04. InputStreamReader
java.io.InputStreamReader je podklasa klase Reader. Njegova uloga je da pretvori bajt-tok (InputStream) u znak-tok (Reader) i pritom podržava zadati skup znakova, čime ostvaruje konverziju između znak-tokova i bajt-tokova.
1) Konstruktori
InputStreamReader(InputStream in): kreira znak-tok koji koristi podrazumevani skup znakova.InputStreamReader(InputStream in, String charsetName): kreira znak-tok sa zadatim skupom znakova.
Primer koda:
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");2) Rešavanje problema sa kodiranjem
Evo primera koji pomoću InputStreamReader rešava problem nečitljivih znakova:
String s = "Chenmo Wang Er!";
try {
// Čuva string u fajl koristeći GBK kodiranje
OutputStreamWriter outUtf8 = new OutputStreamWriter(
new FileOutputStream("logs/test_utf8.txt"), "GBK");
outUtf8.write(s);
outUtf8.close();
// Pretvara bajt-tok u znak-tok koristeći GBK kodiranje
InputStreamReader isr = new InputStreamReader(new FileInputStream("logs/test_utf8.txt"), "GBK");
// Čita znak-tok
int c;
while ((c = isr.read()) != -1) {
System.out.print((char) c);
}
isr.close();
} catch (IOException e) {
e.printStackTrace();
}Pošto je InputStreamReader izvršio konverziju kodiranja nad bajt-tokom, pri čitanju znak-toka kineski znakovi se ispravno dekodiraju, pa se problem nečitljivog teksta izbegava.
05. OutputStreamWriter
java.io.OutputStreamWriter je podklasa klase Writer. Na prvi pogled se može pogrešno pomisliti da pretvara nešto u znak-tok, ali zapravo pretvara znak-tok u bajt-tok — most između znak-tokova i bajt-tokova.
OutputStreamWriter(OutputStream in): kreira znak-tok koji koristi podrazumevani skup znakova.OutputStreamWriter(OutputStream in, String charsetName): kreira znak-tok sa zadatim skupom znakova.
Primer koda:
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("a.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("b.txt") , "GBK");Da bismo podigli efikasnost čitanja i pisanja, preko konverzionog toka obično dodajemo još jedan sloj baferovanog toka. Pogledajmo primer:
try {
// Čita bajt-tok iz fajla koristeći UTF-8 kodiranje
FileInputStream fis = new FileInputStream("test.txt");
// Pretvara bajt-tok u znak-tok koristeći UTF-8 kodiranje
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
// Umotava znak-tok u baferovani tok radi efikasnijeg čitanja
BufferedReader br = new BufferedReader(isr);
// Kreira izlazni tok koristeći UTF-8 kodiranje
FileOutputStream fos = new FileOutputStream("output.txt");
// Umotava izlazni tok u konverzioni tok koristeći UTF-8 kodiranje
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
// Umotava konverzioni tok u baferovani tok radi efikasnijeg pisanja
BufferedWriter bw = new BufferedWriter(osw);
// Čita svaku liniju ulaznog fajla i upisuje u izlazni fajl
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); // Posle svake linije upisuje znak za novi red
}
// Zatvara tokove
br.close();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}U gornjem primeru prvo FileInputStream čita bajt-tok iz fajla kodiran UTF-8. Zatim InputStreamReader pretvara bajt-tok u znak-tok uz UTF-8 kodiranje. Potom se BufferedReader omota oko znak-toka da bi se čitanje učinilo efikasnijim. Zatim se kreira FileOutputStream za izlazni fajl, takođe pod UTF-8. OutputStreamWriter zatim pretvara izlazni tok u znak-tok sa UTF-8, a na kraju BufferedWriter omata konverzioni tok radi efikasnijeg pisanja.
06. Kratak pregled
InputStreamReader i OutputStreamWriter služe da bajt-tok pretvore u znak-tok, odnosno znak-tok u bajt-tok. Najčešće se koriste za rešavanje konverzije između bajt-tokova i znak-tokova: bajt-tok se može pretvoriti u znak-tok sa zadatim skupom znakova, ili znak-tok u bajt-tok sa zadatim skupom znakova.
Najčešće korišćene metode klase InputStreamReader:
read(): čita jedan znak iz ulaznog toka.read(char[] cbuf, int off, int len): čitalenznakova iz ulaznog toka u nizcbuf, počevši od pozicijeoff.ready(): vraća da li je tok spreman za čitanje.close(): zatvara ulazni tok.
Najčešće korišćene metode klase OutputStreamWriter:
write(int c): upisuje jedan znak u izlazni tok.write(char[] cbuf, int off, int len): upisujelenznakova iz nizacbufu izlazni tok, počevši od pozicijeoff.flush(): prazni podatke iz bafera u izlazni tok.close(): zatvara izlazni tok.
Pri upotrebi konverzionih tokova mora se zadati ispravan skup znakova, inače može doći do nečitljivog teksta pri čitanju ili pisanju.
Hvala na čitanju! Ako ti se svideo članak, lajkuj i prati
