Java bajt tokovi: temelj Java IO-a
Mora nam biti jasno: svi podaci fajlova (tekst, video, slike) skladište se u binarnom obliku, a tako se i prenose. Zato bajt tokovi mogu preneti podatke bilo koje vrste fajla.
Izlazni bajt tok (OutputStream)
Klasa java.io.OutputStream je superklasa (roditeljska klasa) izlaznih bajt tokova. Pogledajmo neke zajedničke metode koje ona definiše:
close(): zatvara ovaj izlazni tok i oslobađa sistemske resurse povezane sa njim.flush(): ispira ovaj izlazni tok i prisiljava bajtove iz bafera da se upišu na odredište.write(byte[] b): upisuje b.length bajtova iz datog niza bajtova u ovaj izlazni tok.write(byte[] b, int off, int len): upisuje len bajtova iz datog niza bajtova u ovaj izlazni tok, počevši od ofseta off. Drugim rečima, kreće od off-tog bajta i traje do len bajtova.
Klasa FileOutputStream
Klasa OutputStream ima mnogo podklasa; počećemo od najjednostavnije — FileOutputStream. Samo ime govori da je to izlazni tok za fajlove, namenjen upisivanju podataka u fajl.
1)Konstruktori klase FileOutputStream
- Kreiranje objekta FileOutputStream pomoću imena fajla.
String fileName = "example.txt";
FileOutputStream fos = new FileOutputStream(fileName);Gore navedeni kod kreira objekat FileOutputStream pomoću imena fajla „example.txt" i upisuje podatke u taj fajl. Ako fajl ne postoji, kreira se novi; ako fajl već postoji, stari sadržaj se prepisuje.
- Kreiranje objekta FileOutputStream pomoću objekta File.
File file = new File("example.txt");
FileOutputStream fos = new FileOutputStream(file);Primer korišćenja klase FileOutputStream:
FileOutputStream fos = null;
try {
fos = new FileOutputStream("example.txt");
fos.write("Chenmo Wang Er".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}Gore navedeni kod kreira objekat FileOutputStream, upisuje nisku „Chenmo Wang Er" u fajl example.txt i zatvara izlazni tok na kraju.
2)Upisivanje bajt podataka pomoću FileOutputStream
Upisivanje bajt podataka pomoću FileOutputStream se uglavnom vrši preko metoda write:
write(int b)
write(byte[] b)
write(byte[] b,int off,int len) // kreće od indeksa `off`, `len` bajtova①. Upis jednog bajta: metod write(int b) — pri svakom pozivu upisuje jedan bajt. Kod:
// Kreiramo objekat toka pomoću imena fajla
FileOutputStream fos = new FileOutputStream("fos.txt");
// Upisujemo podatke
fos.write(97); // 1. bajt
fos.write(98); // 2. bajt
fos.write(99); // 3. bajt
// Oslobađamo resurse
fos.close();ASCII vrednost znaka a je 97, ASCII vrednost znaka b je 98, a ASCII vrednost znaka c je 99. Drugim rečima, gore navedeni kod se može napisati i ovako:
// Kreiramo objekat toka pomoću imena fajla
FileOutputStream fos = new FileOutputStream("fos.txt");
// Upisujemo podatke
fos.write('a'); // 1. bajt
fos.write('b'); // 2. bajt
fos.write('c'); // 3. bajt
// Oslobađamo resurse
fos.close();Kada metod write(int b) upisuje jedan bajt, parametar b predstavlja celobrojnu vrednost tog bajta. Pošto bajt ima samo 8 bita, opseg parametra b treba da bude od 0 do 255; vrednosti van tog opsega se skraćuju. Na primer, ako je b jednako -1, skraćuje se na 255; ako je b jednako 256, skraćuje se na 0.
Pri upisivanju parametra b u izlazni tok, metod write(int b) upisuje samo donjih 8 bita parametra b, dok se gornjih 24 bita zanemaruju. Razlog je što se u Javi celobrojni tipovi (uključujući bajt, short, int, long) u memoriji predstavljaju u obliku komplementa dvojke (two's complement). Kada se celobrojna vrednost prosledi metodu write(int b), metod je pretvara u tip bajt — zadržava samo donjih 8 bita komplementa dvojke, a gornjih 24 bita odbacuje.
Na primer, ako želimo da upišemo celi broj 0x12345678, njegov zapis u komplementu dvojke je 0001 0010 0011 0100 0101 0110 0111 1000. Kada se ovaj broj upisuje metodom write(int b), upisuju se samo donjih 8 bita komplementa dvojke — 0111 1000 — dok se gornjih 24 bita 0001 0010 0011 0100 0101 0110 zanemaruju. To je razlog zašto se gornjih 24 bita parametra b odbacuju.
0111 1000 je 8-bitni binarni broj koji odgovara decimalnoj vrednosti 120, a u ASCII tabeli to je malo slovo „x". Prema tome, ako metodom write(int b) upišemo bajt 0x78 (decimalno 120), rezultat je malo slovo „x".
Proverimo to:
FileOutputStream fos = null;
try {
fos = new FileOutputStream("example.txt");
fos.write(120);
fos.write('x');
fos.write(0x12345678);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}Pogledajmo rezultat:

Zaista, tri x-a.
②. Upis niza bajtova: write(byte[] b). Primer koda:
// Kreiramo objekat toka pomoću imena fajla
FileOutputStream fos = new FileOutputStream("fos.txt");
// Pretvaramo nisku u niz bajtova
byte[] b = "Chenmo Wang Er je pomalo zgodan".getBytes();
// Upisujemo podatke iz niza bajtova
fos.write(b);
// Oslobađamo resurse
fos.close();③. Upis niza bajtova zadate dužine: write(byte[] b, int off, int len). Primer koda:
// Kreiramo objekat toka pomoću imena fajla
FileOutputStream fos = new FileOutputStream("fos.txt");
// Pretvaramo nisku u niz bajtova
byte[] b = "abcde".getBytes();
// Kreće od indeksa 2, 2 bajta. Indeks 2 je 'c', dva bajta znače 'cd'.
fos.write(b,2,2);
// Oslobađamo resurse
fos.close();3)Dodavanje podataka i prelazak u novi red pomoću FileOutputStream
U gore navedenim primerima, pri svakom pokretanju programa kreira se novi objekat izlaznog toka, pa se podaci u fajlu brišu. Šta ako želimo da zadržimo postojeće podatke u fajlu i samo dodajemo nove? I kako da ostvarimo prelazak u novi red?
Zapravo je vrlo jednostavno.
Naučimo još dva konstruktora klase FileOutputStream:
- Kreiranje objekta FileOutputStream pomoću imena fajla i zastavice za dodavanje
String fileName = "example.txt";
boolean append = true;
FileOutputStream fos = new FileOutputStream(fileName, append);Gore navedeni kod kreira objekat FileOutputStream pomoću imena fajla „example.txt" i zastavice za dodavanje, čime se podaci nadovezuju na kraj fajla. Ako fajl ne postoji, kreira se novi; ako fajl već postoji, podaci se dodaju na kraj.
- Kreiranje objekta FileOutputStream pomoću objekta File i zastavice za dodavanje
File file = new File("example.txt");
boolean append = true;
FileOutputStream fos = new FileOutputStream(file, append);Gore navedeni kod kreira objekat FileOutputStream pomoću objekta File i zastavice za dodavanje, čime se podaci nadovezuju na kraj fajla.
Kod oba konstruktora, drugi parametar je vrednost tipa boolean: true znači dodavanje podataka, false znači prepisivanje (brisanje postojećih podataka).
Kod za dodavanje podataka izgleda ovako:
// Kreiramo objekat toka pomoću imena fajla
FileOutputStream fos = new FileOutputStream("fos.txt",true);
// Pretvaramo nisku u niz bajtova
byte[] b = "abcde".getBytes();
// Upisuje počevši od indeksa 2, 2 bajta. Indeks 2 je 'c', dva bajta znače 'cd'.
fos.write(b);
// Oslobađamo resurse
fos.close();Ako pokrenete kod više puta, primetićete da se podaci neprestano dodaju.
U Windows sistemu, znak za novi red je \r\n. Konkretan kod:
String filename = "example.txt";
FileOutputStream fos = new FileOutputStream(filename, true); // Režim dodavanja
String content = "Chenmo Wang Er\r\n"; // Kombinacija znaka za povratak i novi red
fos.write(content.getBytes());
fos.close();U macOS sistemu, znak za novi red je \n. Konkretan kod:
String filename = "example.txt";
FileOutputStream fos = new FileOutputStream(filename, true); // Režim dodavanja
String content = "Chenmo Wang Er\n"; // Samo znak za novi red
fos.write(content.getBytes());
fos.close();Da ovde kažemo još nešto o znaku za povratak (carriage return) i znaku za novi red (line feed).
Znak za povratak (\r) i znak za novi red (\n) su uobičajeni kontrolni znakovi u računarima, koji se koriste za označavanje kraja linije ili prelazak u novi red. Njihova upotreba se može razlikovati u različitim operativnim sistemima i programskim jezicima.
U Windows sistemu se obično koristi kombinacija znaka za povratak i znaka za novi red (\r\n) za označavanje kraja linije. U tekstualnim fajlovima se svaka linija završava tom kombinacijom. Razlog je što su rani štampači i terminali zahtevali tu kombinaciju da bi dovršili kraj linije i prelazak u novi red. U Windowsu, alati poput tekst editora i komandnih terminala podržavaju tu kombinaciju kao oznaku kraja linije.
S druge strane, u macOS i Linux sistemima se obično koristi samo znak za novi red (\n) za označavanje kraja linije. U tekstualnim fajlovima se na kraju svake linije nalazi samo taj znak. Razlog je što su terminali u ranim Unix sistemima zahtevali samo znak za novi red za završetak linije i prelazak u sledeći red. U macOS-u i Linuxu, tekst editori i terminali podržavaju znak za novi red kao oznaku kraja linije.
U programskim jezicima se znak za povratak i znak za novi red često koriste i za operacije nad niskama. Na primer, u Javi se znak za povratak u niski može predstaviti sa \r, a znak za novi red sa \n. Prilikom čitanja i pisanja fajlova preko ulazno-izlaznih tokova, takođe treba voditi računa o načinu upotrebe ovih znakova i o razlikama između operativnih sistema.
Ulazni bajt tok (InputStream)
Klasa java.io.InputStream je superklasa (roditeljska klasa) ulaznih bajt tokova. Pogledajmo neke njene zajedničke metode:
close(): zatvara ovaj ulazni tok i oslobađa sistemske resurse povezane sa njim.int read(): čita sledeći bajt iz ulaznog toka.read(byte[] b): int vrednost koju metod vraća predstavlja broj pročitanih bajtova — koliko je pročitao, toliko vrati; kada više nema podataka, vraća -1.
Klasa FileInputStream
Klasa InputStream ima mnogo podklasa; počećemo od najjednostavnije — FileInputStream. Samo ime govori da je to ulazni tok za fajlove, namenjen čitanju podataka iz fajla.
1)Konstruktori klase FileInputStream
FileInputStream(String name): kreira objekat FileInputStream i otvara fajl sa zadatim imenom radi čitanja. Ime fajla se navodi parametrom name. Ako fajl ne postoji, baca se izuzetak FileNotFoundException.FileInputStream(File file): kreira objekat FileInputStream i otvara fajl koji predstavlja zadati objekat File, radi čitanja.
Primer koda:
// Kreiramo objekat FileInputStream
FileInputStream fis = new FileInputStream("test.txt");
// Čitamo sadržaj fajla
int data;
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
// Zatvaramo ulazni tok
fis.close();2)Čitanje bajt podataka pomoću FileInputStream
①. Čitanje jednog bajta: metod read() čita jedan bajt i vraća njegovu celobrojnu vrednost. Ako se stigne do kraja fajla, vraća -1. Ako tokom čitanja dođe do greške, baca se izuzetak IOException.
Primer koda:
// Kreiramo objekat FileInputStream
FileInputStream fis = new FileInputStream("test.txt");
// Čitamo sadržaj fajla
int data;
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
// Zatvaramo ulazni tok
fis.close();②. Čitanje pomoću niza bajtova: metod read(byte[] b) čita najviše b.length bajtova iz ulaznog toka i smešta ih u bafer niz b.
Primer koda:
// Kreiramo objekat FileInputStream
FileInputStream fis = new FileInputStream("test.txt");
// Čitamo sadržaj fajla u bafer
byte[] buffer = new byte[1024];
int count;
while ((count = fis.read(buffer)) != -1) {
System.out.println(new String(buffer, 0, count));
}
// Zatvaramo ulazni tok
fis.close();3)Kopiranje slike bajt tokom FileInputStream
Princip je vrlo jednostavan: podatke slike učitamo u ulazni bajt tok, a zatim ih preko izlaznog bajt toka upišemo u fajl.
Primer koda:
// Kreiramo objekat FileInputStream za čitanje originalne slike
FileInputStream fis = new FileInputStream("original.jpg");
// Kreiramo objekat FileOutputStream za upis kopirane slike
FileOutputStream fos = new FileOutputStream("copy.jpg");
// Kreiramo niz bafera za čuvanje pročitanih podataka
byte[] buffer = new byte[1024];
int count;
// Čitamo originalnu sliku i upisujemo podatke u kopiju
while ((count = fis.read(buffer)) != -1) {
fos.write(buffer, 0, count);
}
// Zatvaramo ulazni i izlazni tok
fis.close();
fos.close();Gore navedeni kod kreira objekat FileInputStream za čitanje originalne slike i objekat FileOutputStream za upis kopirane slike. Zatim, pomoću while petlje, bajt po bajt čita podatke iz originalne slike i upisuje ih u kopiju. Na kraju se zatvaraju ulazni i izlazni tok i oslobađaju se resursi.
Rezime
InputStream je apstraktna klasa ulaznih bajt tokova; ona definiše metode za čitanje bajt podataka, kao što su read(), read(byte[] b), read(byte[] b, int off, int len) i slično. OutputStream je apstraktna klasa izlaznih bajt tokova; ona definiše metode za upis bajt podataka, kao što su write(int b), write(byte[] b), write(byte[] b, int off, int len) i slično. Ove dve apstraktne klase su temelj bajt tokova.
FileInputStream je tok za čitanje bajt podataka iz fajla i nasleđuje se iz InputStream. FileOutputStream je tok za upis bajt podataka u fajl i nasleđuje se iz OutputStream. Ove dve klase su među najčešće korišćenim implementacijama bajt tokova.
