Šta su tokovi?
Tok je komunikaciona putanja između izvora neke informacije i odredišta. Postoje ulazni i izlazni tokovi. Ulazni tok vam omogućava da čitate podatke iz nekog izvora, a izlazni da upisujete podatke u neko odredište. Pri tome, sam program ne mora da ima bilo kakvu informaciju o izvoru ili odredištu.
Tokovi u Javi se realizuju pomoću dve klase, InputStream i OutputStream, i nekoliko podklasa koje nasleđuju ove dve.
InputStream klasa – čitanje bajtova
Ovo je apstraktna klasa koja definiše osnovni način na koji se čita tok bajtova iz nekog izvora. Vrsta i identitet izvora, kao i način kreiranja i transporta ovih informacija je irelevantan. Svi drugi ulazni tokovi nasleđuju ovu klase. Da bi vaš kod koristio klase tokova, neophodno je da uvezete paket java.io u kome se ove klase nalaze.
Klasa InputStream ima nekoliko metoda, koje nasleđuju i njene podklase. Zbog toga su ovi metodi dostupni iz svakog ulaznog toka. Takođe, metodi ove klase mogu da proizvedu izuzetak IOException (na primer kada je ulazni tok zatvoren), pa je neophodno da on bude obrađen pomoću try/catch naredbe.
Metod read()
Najvažniji metod ulaznih tokova je metod read(). Ovaj metod čeka dok ulazni podaci ne budu
dostupni, a zatim ih učitava. Postoje tri osnovne varijante ovog metoda.
public abstract int read() throws IOException
Ovako definisan metod čita sledeći bajt iz ulaznog toka i kao vrednost vraća celobrojnu vrednost
pročitanog bajta. Ukoliko nije pročitan bajt, ovaj metod vraća vrednost -1.
public int read(byte[] b) throws IOException
Ovaj metod čita određen broj bajtova iz ulaznog toka i čuva ih u nizu b. Vrednost koju vraća
predstavlja broj zaista pročitanih bajtova.
public int read(byte[] b, int pocetak, int duzina) throws IOException
Ovako definisan metod read() čita broj bajtova određen argumentom duzina, počevši od pozicije
određene argumentom pocetak i smešta ih u niz bajtova b. Kao vrednost, ovaj metod vraća broj
zaista pročitanih bajtova.
Na primer, sledeća linija koda pokušava da pročita od 100-og do 399-og bajta.
s.read(b, 100, 300);
Metod skip()
Ovaj metod se koristi kada želite da počnete čitanje od neke određene lokacije ili da preskočite
nekoliko bajtova. Na primer, s.skip(1024) preskače 1024 bajta u ulaznom toku. Ovaj metod vraća
vrednost koja predstavlja stvarni broj bajtova koji su preskočeni. Ova vrednost može da se
razlikuje od one koja je zadata, na primer kada je zadato da se preskoči veći broj bajtova nego
što ih zaista ima u ulaznom toku.
Metodi mark() i reset()
Metod mark() obeležava trenutnu poziciju u ulaznom toku, dok metod reset() vraća na tu poziciju.
Na žalost, ovi metodi nisu podržani u svakom ulaznom toku. Pogledajte sledeći kod:
InputStream s = uzmiNekiUlazniTok();
if (s.markSupported()) { // da li je markiranje podrzano ?
. . . // citaj iz ulaznog toka
s.mark(1024);
. . . // citaj manje od 1024 bajtova
s.reset();
. . . // nakon reset() moguće je ponovo čitati bajtove
} else {
. . . // markiranje nije podrzano, uradi nesto drugo
}
U ovom primeru se prvo vrši provera da li je markiranje podržano za dati ulazni tok pomoću metoda
markSupported(). U delu koda koji se izvršava ukoliko je markiranje podržano, moguće je čitanje iz
ulaznog toka proizvoljnog broja bajtova pre poziva metoda mark(). Argument ovog metoda
označava koliko bajtova je moguće pročitati pre poziva reset(), tj. koliko dugo se “pamti”
markirana pozicija. Poziv metoda reset() vraća ulazni tok na poziciju na kojoj je pozvan metod
mark().
Metod available()
Ukoliko želite da znate koliko je bajtova u toku u određenom trenutku, koristite metod available().
Na žalost, nemaju svi tokovi implementiran ovaj metod. Kod nekih on uvek vraća vrednost 0, pa
nije dobra praksa oslanjati se na njega.
Metod close()
Obzirom da ne morate da znate koje resurse otvoreni tok koristi, niti kako da ih pravilno obradite
nakon što je čitanje toka završeno, trebali biste da eksplicitno zatvorite tok, kako bi on oslobodio
resurse. Ovo nije neophodno, obzirom da će sam garbage collector to da uradi, ali je dobra
praksa, jer omogućava ponovno otvaranje toka pre nego što ga garbage collector uništi.
Kada se uzme u obzir sve prethodno rečeno, deo koda koji obrađuje tokove bi trebalo da izgleda
slično kao dati primer koda:
InputStream s = napraviNoviUlazniStream();
if (s != null) {
try {
. . .
} catch (Exception e) {
. . .
} finally {
s.close();
}
}
Podklase klase InputStream
ByteArrayInputStream
Ova klasa kreira ulazni tok od niza bajtova.
byte[] bafer = new byte[1024];
popuniSaKorisnimPodacima(bafer);
InputStream s = new ByteArrayInputStream(bafer);
Ova klasa ima i konstruktor pomoću koga možete samo deo podataka da ubacite u tok:
InputStream s = new ByteArrayInputStream(bafer, 100, 300);
Ovako kreiran ulazni tok uzima samo 300 članova niza bafer, polazeći od 100-og.
FileInputStream
Tokovi se najčešće koriste za čitanje podataka iz fajlova. Za ovu svrhu koristimo klasu
FileInputStream. Na primer:
InputStream s = new FileInputStream(“F:/BARN/KursJava/imefajla”);
Prethodna naredba kreira novi ulazni tok koji odgovara određenom fajlu.
BufferedInputStream
Klase pomenute do sada koriste tzv. nebaferovan ulaz. To znači da je svako čitanje podataka
rukovođeno od strane operativnog sistema. Ovo obično čini ceo program manje efikasnim, obzirom
da svaki zahtev za čitanje pokreće i neke druge operacije kao što su pristup disku, mrežna
aktivnost ili dr. Da bi povećala efiksanost, Java platforma implementira tzv. baferovane tokove
ulaza i izlaza. Baferovani tokovi ulaza čitaju iz dela memorije koja se naziva buffer.
Ovo je jedna od najkorisnijih klasa od svih tokova. Pomoću nje se svi nebaferovani tokovi
konvertuju u baferovane.
InputStream s = new BufferedInputStream(new FileInputStream("fajl"));
Takođe, ovo je jedina klasa koja implementira metode mark() i reset().
DataInputStream
Ova klasa omogućava da iz ulaznog toka ne čitate samo bajtove, već i mnogo kompleksnije podatke. Umesto metoda read(), ova klasa koristi metode kao što su readBoolean(), readInt(), readDouble() itd. Ovi metodi proizvode izuzetak tipa EOFException, pa je neophodno obraditi ga u kodu. Pogledajte sledeći primer:
DataInputStream s = new DataInputStream(new
BufferedInputStream(new FileInputStream(imeFajla)));
try {
while (true) {
cena = s.readDouble();
kolicina = s.readInt();
opis = s.readUTF();
System.out.format(
"Narucili ste " + kolicina +" komada " + opis +
" po ceni od " + cena);
}
} catch (EOFException e) {
}
U prvoj naredbi, otvara se ulazni tok tipa FileInputStream za fajl imeFajla, zatim se ovaj tok konvertuje u baferovan tok tipa BufferedInputStream, a onda se na osnovu njega kreira novi ulazni tok tip DataInputStream, koji omogućava čitanja kompleksnijih podataka. Ovi podaci se zatim čitaju pomoću metoda klase DataInputStream.
Ovde treba napomenuti sledeće. Ulazni tokovi se sastoje od binarnih podataka. Klasa DataInputStream jednostavno čita te binarne podatke i konvertuje ih u odgovarajuće tipove u zavisnosti od metoda koji je pozvan. Na primer, metod readDouble() čita 8 bajtova i od njih formira vrednost koju vraća. Ne posoji nikakva kontrola da li pročitani bajtovi zaista predstavljaju broj tipa double u izvornom fajlu. Stoga je prilikom korišćenja ove klase neophodno poznavanje strukture podatka koji se čitaju.
Reader klasa – čitanje karaktera
Sve prethodne klase su nasleđivale klasu InputStream, što znači da su radile sa tokovima bajtova. Za čitanje karaktera postoji posebna klasa, klasa Reader. Ona je definisana kao apstraktna. Jedini metodi koji su takođe definisani kao apstraktni i koje njene podklase moraju da implementiraju su metod read() i metod close(). Pogledajmo neke od postojećih podklasa klase Reader.
InputStreamReader
Ova klasa je posrednik između toka bajtova i toka karaktera. Ona čita bajtove i prevodi ih karaktere, koristeći informaciju o načinu kodiranja karaktera.
FileReader
Ova klasa je direktna podklasa klase InputStreamReader i namenjena je čitanju karaktera iz fajlova.
BufferedReader
Ova klasa čita neki tok karaktera i smešta ih u bafer, kako bi omogućila efikasno čitanje karaktera, nizova i linija teksta. Ova klasa se koristi veoma često za poboljšanje efikasnosti rada sa bilo kojim drugim tokom karaktera:
BufferedReader in = new BufferedReader(new InputStreamReader(ulaz));
ili
BufferedReader in = new BufferedReader(new FileReader("ulaz.txt"));
Takođe, ova klasa pored metoda read(), koji čita jedan karakter, ima i metod readLine() koji čita celu liniju teksta i konvertuje je u String objekat.
Standardni ulaz
Klase koje su do sada pomenute obično podrazumevaju da se podaci koji se čitaju nalaze u nekom fajlu ili da se obezbeđuju programski. Međutim, u realnim aplikacijama je potrebno u toku izvršavanja programa uzeti neke podatke od korisnika. Korisnik te podatke obično obezbeđuje jednostavnim kucanjem na tastaturi. Klasa koja obezbeđuje preuzimanje (čitanje) onoga što korisnik ukuca jeste klasa System koja ima klasnu promenljivu in. Njena deklaracija je:
public static final InputStream in;
System.in odgovara tzv. standardnom ulaznom toku, tj. tastaturi. Ovaj tok je uvek otvoren i spreman da preuzme podatke od korisnika. Standardni ulazni tok pripada klasi InputStream, što znači da je to u stvari tok bajtova, a ne karaktera, kako bismo možda očekivali. Zbog toga se čitanje iz standardnog ulaznog toka obično vrši pomoću klase InputStreamReader, a ne direktnim pozivom metoda read() toka System.in.
InputStreamReader ulaz = new InputStreamReader(System.in);