Java File: početak i kraj IO tokova
U IO operacijama je rad sa fajlovima relativno složen, ali i deo koji se najčešće koristi — u skoro svakom našem projektu nalazi se neka utility klasa koja se zove FileUtil ili FileUtils.
Klasa java.io.File je klasa namenjena isključivo radu sa fajlovima. Obratite pažnju da može da operiše samo nad fajlom kao takvim, a ne i nad njegovim sadržajem — za operacije nad sadržajem neophodni su ulazni i izlazni tokovi.
Klasa File je apstraktna reprezentacija fajlova i direktorijuma, prvenstveno namenjena operacijama kreiranja, pronalaženja i brisanja fajlova i direktorijuma.
Kako da razumete prethodne dve rečenice? Zapravo je vrlo jednostavno!
Prva rečenica kaže da File nema veze sa tokovima — klasa File ne može da čita i piše fajlove, odnosno ne može da vrši ulaz i izlaz!
Druga rečenica kaže da File može da predstavlja D:\\direktorijum1 i D:\\direktorijum1\\fajl.txt — prvo je folder (Directory, odnosno direktorijum), drugo je fajl (file), a klasa File služi upravo za rad sa ta dva.
Konstruktor klase File
U Javi je sve objekat, pa tako i klasa File. Za bilo koji objekat treba krenuti od njegovog konstruktora, zato ćemo analizirati konstruktor klase File.
Tri konstruktora se najčešće koriste:
File(String pathname): kreira novu instancu File na osnovu zadate putanje.File(String parent, String child): kreira novu instancu File na osnovu roditeljske putanje (string) i podređene putanje.File(File parent, String child): kreira novu instancu File na osnovu roditeljske putanje (File) i podređenog imena putanje kao stringa.
Tekstualni opis nije dovoljno živ, vizuelan i jasan? Nema problema, uz primere odmah postaje živo i jasno. Evo koda:
// Ime putanje fajla
String path = "/Users/username/123.txt";
File file1 = new File(path);
// Ime putanje fajla
String path2 = "/Users/username/1/2.txt";
File file2 = new File(path2); -------------ekvivalentno /Users/username/1/2.txt
// Preko roditeljske i podređene putanje (stringovi)
String parent = "/Users/username/aaa";
String child = "bbb.txt";
File file3 = new File(parent, child); --------ekvivalentno /Users/username/aaa/bbb.txt
// Preko roditeljskog File objekta i podređene putanje (string)
File parentDir = new File("/Users/username/aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child); --------ekvivalentno /Users/username/aaa/bbb.txtObratite pažnju: macOS koristi regularnu kosu crtu (/) kao separator putanje, dok Windows koristi obrnutu kosu crtu (\) kao separator putanje. Zato kada naiđete na separator putanje, nemojte direktno pisati / ili \.
Java nudi platformski nezavisan način za dobijanje separatora putanje — korišćenje File.separator; ovo svojstvo automatski vraća ispravan separator putanje u zavisnosti od operativnog sistema.
Stvari na koje treba obratiti pažnju kod klase File:
- Jedan File objekat predstavlja jedan stvarno postojeći fajl ili direktorijum na disku.
- Konstruktor klase File ne proverava da li taj fajl ili direktorijum zaista postoji, pa zato postojanje ili nepostojanje fajla/direktorijuma na toj putanji ne utiče na kreiranje File objekta.
Uobičajene metode klase File
Uobičajene metode klase File uglavnom se dele na metode za dobijanje informacija, metode za apsolutne i relativne putanje, metode za proveru i metode za kreiranje i brisanje.
1) Metode za dobijanje informacija
getAbsolutePath(): vraća apsolutnu putanju ovog File-a.getPath(): rezultat je isti kao kod getAbsolutePath.getName(): vraća ime fajla ili direktorijuma.length(): vraća dužinu fajla, u bajtovima.
Test kod je ispod [imajte na umu da testove prilagodite folderima na vašem računaru]:
File f = new File("/Users/username/aaa/bbb.java");
System.out.println("Apsolutna putanja fajla:"+f.getAbsolutePath());
System.out.println("Konstrukciona putanja fajla:"+f.getPath());
System.out.println("Ime fajla:"+f.getName());
System.out.println("Dužina fajla:"+f.length()+" bajtova");
File f2 = new File("/Users/username/aaa");
System.out.println("Apsolutna putanja direktorijuma:"+f2.getAbsolutePath());
System.out.println("Konstrukciona putanja direktorijuma:"+f2.getPath());
System.out.println("Ime direktorijuma:"+f2.getName());
System.out.println("Dužina direktorijuma:"+f2.length());Napomena: length() označava dužinu fajla; kada File objekat predstavlja direktorijum, povratna vrednost nema značenje.
2) Apsolutne i relativne putanje
Apsolutna putanja je kompletna putanja koja polazi od korenog direktorijuma fajl sistema; ona opisuje tačnu poziciju fajla ili direktorijuma u fajl sistemu. U Windows sistemama apsolutna putanja obično počinje slovom particije (npr. C:), na primer "C:\Program Files\Java\jdk1.8.0_291\bin\java.exe". U macOS i Linux sistemima apsolutna putanja obično počinje kosom crtom (/), na primer "/usr/local/bin/python3".
Relativna putanja je putanja u odnosu na trenutni radni direktorijum; ona opisuje pozicioni odnos između fajla ili direktorijuma i trenutnog radnog direktorijuma. U Javi je relativna putanja obično u odnosu na direktorijum u kojem se nalazi tekući Java program, na primer "config/config.properties". Ako je trenutni radni direktorijum "/Users/username/project", onda relativna putanja "config/config.properties" označava "/Users/username/project/config/config.properties".
Napomene:
- U Windows operativnom sistemu fajl sistem podrazumevano ne razlikuje velika i mala slova, što znači da se u fajl sistemu velika i mala slova u imenima fajlova i putanjama mogu mešati. Na primer, "
C:\Users\username\Documents\example.txt" i "C:\Users\Username\Documents\Example.txt" označavaju isti fajl. Međutim, Windows operativni sistem nudi opciju razlikovanja velikih i malih slova koja se može omogućiti pri formatiranju diska, pa će tada fajl sistem razlikovati velika i mala slova. - U macOS, Linux i drugim Unix sistemima fajl sistem podrazumevano razlikuje velika i mala slova. Na primer, u macOS sistemu "
/Users/username/Documents/example.txt" i "/Users/username/Documents/Example.txt" označavaju dva različita fajla.
// Primer apsolutne putanje
File absoluteFile = new File("/Users/username/example/test.txt");
System.out.println("Apsolutna putanja:" + absoluteFile.getAbsolutePath());
// Primer relativne putanje
File relativeFile = new File("example/test.txt");
System.out.println("Relativna putanja:" + relativeFile.getPath());3) Metode za proveru
exists(): proverava da li fajl ili direktorijum postoji.isDirectory(): proverava da li je direktorijum.isFile(): proverava da li je fajl.
Demonstracija metoda, kod je ispod:
File file = new File("/Users/username/example");
// Proveri da li fajl ili direktorijum postoji
if (file.exists()) {
System.out.println("fajl ili direktorijum postoji");
} else {
System.out.println("fajl ili direktorijum ne postoji");
}
// Proveri da li je direktorijum
if (file.isDirectory()) {
System.out.println("jeste direktorijum");
} else {
System.out.println("nije direktorijum");
}
// Proveri da li je fajl
if (file.isFile()) {
System.out.println("jeste fajl");
} else {
System.out.println("nije fajl");
}4) Metode za kreiranje i brisanje
createNewFile(): ako fajl ne postoji, kreira novi prazan fajl i vraćatrue; ako fajl postoji, ne kreira fajl i vraćafalse.delete(): briše fajl ili direktorijum. Ako je u pitanju direktorijum, može se obrisati samo ako je prazan.mkdir(): može kreirati samo jedan nivo direktorijuma; ako roditeljski direktorijum ne postoji, kreiranje ne uspeva. Vraća true za uspeh, odnosno false za neuspeh.mkdirs(): može kreirati više nivoa direktorijuma; ako roditeljski direktorijum ne postoji, kreira se zajedno sa njim. Vraća true za uspeh, a false znači da kreiranje nije uspelo ili direktorijum već postoji.
U praksi se najčešće koristi mkdirs();
Test metoda, kod je ispod:
// Kreiraj fajl
File file = new File("/Users/username/example/test.txt");
if (file.createNewFile()) {
System.out.println("Kreiranje fajla uspelo: " + file.getAbsolutePath());
} else {
System.out.println("Kreiranje fajla nije uspelo: " + file.getAbsolutePath());
}
// Obriši fajl
if (file.delete()) {
System.out.println("Brisanje fajla uspelo: " + file.getAbsolutePath());
} else {
System.out.println("Brisanje fajla nije uspelo: " + file.getAbsolutePath());
}
// Kreiraj više nivoa direktorijuma
File directory = new File("/Users/username/example/subdir1/subdir2");
if (directory.mkdirs()) {
System.out.println("Kreiranje direktorijuma uspelo: " + directory.getAbsolutePath());
} else {
System.out.println("Kreiranje direktorijuma nije uspelo: " + directory.getAbsolutePath());
}5) Pregled direktorijuma
String[] list(): vraća niz Stringova koji predstavlja sve podfajlove ili poddirektorijume u ovom File direktorijumu.File[] listFiles(): vraća niz File objekata koji predstavlja sve podfajlove ili poddirektorijume u ovom File direktorijumu.
File directory = new File("/Users/itwanger/Documents/Github/paicoding");
// Izlistaj imena fajlova u direktorijumu
String[] files = directory.list();
System.out.println("Imena fajlova u direktorijumu:");
for (String file : files) {
System.out.println(file);
}
// Izlistaj fajlove i poddirektorijume u direktorijumu
File[] filesAndDirs = directory.listFiles();
System.out.println("Fajlovi i poddirektorijumi u direktorijumu:");
for (File fileOrDir : filesAndDirs) {
if (fileOrDir.isFile()) {
System.out.println("Fajl: " + fileOrDir.getName());
} else if (fileOrDir.isDirectory()) {
System.out.println("Direktorijum: " + fileOrDir.getName());
}
}listFiles pri dobijanju fajlova ili poddirektorijuma iz zadatog direktorijuma mora ispuniti sledeća dva uslova:
- Zadati direktorijum mora postojati
- Zadato mora biti direktorijum, inače lako može doći do NullPointerException izuzetka
6) Rekurzivni pregled
Bez puno reči, evo koda:
public static void main(String[] args) {
File directory = new File("/Users/itwanger/Documents/Github/paicoding");
// Rekurzivno obiđi fajlove i poddirektorijume u direktorijumu
traverseDirectory(directory);
}
public static void traverseDirectory(File directory) {
// Izlistaj sve fajlove i poddirektorijume u direktorijumu
File[] filesAndDirs = directory.listFiles();
// Obiđi svaki fajl i poddirektorijum
for (File fileOrDir : filesAndDirs) {
if (fileOrDir.isFile()) {
// Ako je fajl, ispiši ime fajla
System.out.println("Fajl: " + fileOrDir.getName());
} else if (fileOrDir.isDirectory()) {
// Ako je direktorijum, rekurzivno obiđi poddirektorijum
System.out.println("Direktorijum: " + fileOrDir.getName());
traverseDirectory(fileOrDir);
}
}
}RandomAccessFile
RandomAccessFile je u Javi vrlo posebna klasa — može se koristiti i za čitanje i za pisanje fajlova. Za razliku od drugih IO klasa (poput FileInputStream i FileOutputStream), RandomAccessFile omogućava vam da skočite na bilo koju poziciju u fajlu i odande počnete sa čitanjem ili pisanjem. To je čini posebno pogodnom za scenarije u kojima je potrebno nasumično pristupanje podacima u fajlu, kakav je slučaj sa sistemima baza podataka.
Ispod je primer korišćenja RandomAccessFile, uključujući pisanje i čitanje fajla:
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void main(String[] args) {
String filePath = "logs/javabetter/itwanger.txt";
try {
// Koristi RandomAccessFile za pisanje u fajl
writeToFile(filePath, "Hello, Chenmo Wang Er!");
// Koristi RandomAccessFile za čitanje iz fajla
String content = readFromFile(filePath);
System.out.println("Sadržaj fajla: " + content);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeToFile(String filePath, String content) throws IOException {
try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "rw")) {
// Pomeri pokazivač fajla na kraj fajla (ovde dodaj sadržaj)
randomAccessFile.seek(randomAccessFile.length());
// Upiši sadržaj
randomAccessFile.writeUTF(content);
}
}
private static String readFromFile(String filePath) throws IOException {
StringBuilder content = new StringBuilder();
try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r")) {
// Pomeri pokazivač fajla na početak fajla (čitaj od početka)
randomAccessFile.seek(0);
content.append(randomAccessFile.readUTF());
}
return content.toString();
}
}Da bismo izbegli problem nečitljivih (mojibake) znakova, koristimo metode writeUTF i readUTF klase RandomAccessFile, koje obrađuju stringove pomoću UTF-8 kodiranja. Slobodno pokrenite ovaj kod i isprobajte ga.

U nastavku ćemo detaljno predstaviti konstruktor i uobičajene metode klase RandomAccessFile.
Konstruktor
Klasa RandomAccessFile uglavnom ima dva konstruktora:
RandomAccessFile(File file, String mode): kreira novu instancu RandomAccessFile na osnovu zadatog objekta fajla i režima pristupa.RandomAccessFile(String name, String mode): kreira novu instancu RandomAccessFile na osnovu zadatog imena fajla i režima pristupa.
Vrednost režima pristupa mode može biti:
- "r": otvara fajl u režimu samo za čitanje. Poziv bilo koje write metode rezultujućeg objekta dovešće do IOException.
- "rw": otvara fajl u režimu čitanja i pisanja. Ako fajl ne postoji, biće kreiran.
- "rws": otvara fajl u režimu čitanja i pisanja i zahteva da se svaka izmena sadržaja ili metapodataka odmah upiše na osnovni uređaj za skladištenje. Ovaj režim je sinhronizovan i osigurava da podaci ne budu izgubljeni pri padu sistema.
- "rwd": slično kao "rws", otvara fajl u režimu čitanja i pisanja, ali zahteva da se samo izmene sadržaja fajla odmah upisuju. Metapodaci se mogu upisivati sa zadrškom.
Glavne metode
long getFilePointer(): vraća trenutnu poziciju pokazivača fajla.long length(): vraća dužinu ovog fajla.int read(): čita jedan bajt podataka iz ovog fajla.int read(byte[] b): čita podatke u bajtovima iz ovog fajla i smešta ih u zadati niz bajtova.int read(byte[] b, int off, int len): čita podatke u bajtovima iz ovog fajla i smešta ih u zadati niz bajtova, počevši od ofseta off, najviše len bajtova.String readLine(): čita jedan red teksta iz ovog fajla.readUTF(): čita UTF-8 kodirani string iz fajla. Ova metoda prvo čita dvobajtnu informaciju o dužini, a zatim na osnovu te dužine čita UTF-8 bajtove stringa. Na kraju, ti bajtovi se konvertuju u Java string. To znači da kada readUTF metodom čitate string, morate osigurati da je string u fajlu zapisan writeUTF metodom, kako bi se informacija o dužini i način kodiranja poklapali.void seek(long pos): postavlja pokazivač fajla na poziciju pos u fajlu.void write(byte[] b): upisuje sve bajtove zadatog niza bajtova u ovaj fajl.void write(byte[] b, int off, int len): upisuje deo zadatog niza bajtova u ovaj fajl, počevši od ofseta off, ukupno len bajtova.void write(int b): upisuje zadati bajt u ovaj fajl.writeUTF(String str): upisuje string u fajl koristeći UTF-8 kodiranje. Ova metoda prvo upisuje dvobajtnu informaciju o dužini, koja predstavlja dužinu UTF-8 bajtova stringa, a zatim upisuje same UTF-8 bajtove. Prema tome, kada writeUTF-om upisujete string, stvarni broj upisanih bajtova biće za dva bajta veći od dužine UTF-8 bajtova stringa. Ta dva bajta služe da se pri čitanju stringa odredi ispravna dužina stringa.
Još jedan primer — uz prethodno objašnjenje, potpuno ćete savladati RandomAccessFile.
File file = new File("logs/javabetter/itwanger.txt");
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
// Upiši u fajl
raf.writeUTF("Hello, Chenmo Wang Er!");
// Pomeri pokazivač fajla na početak fajla
raf.seek(0);
// Pročitaj sadržaj fajla
String content = raf.readUTF();
System.out.println("Sadržaj: " + content);
} catch (IOException e) {
e.printStackTrace();
}U ovom primeru prvo kreiramo objekat fajla pod imenom itwanger.txt. Zatim taj fajl otvaramo pomoću RandomAccessFile u režimu čitanja i pisanja.
Zatim metodom writeUTF upisujemo string "Hello, Chenmo Wang Er!" u fajl. Nakon toga, metodom seek pomeramo pokazivač fajla na početak fajla i metodom readUTF čitamo sadržaj fajla. Izlaz treba da bude "Hello, Chenmo Wang Er!".
Na kraju, koristimo iskaz try-with-resources kako bismo osigurali da RandomAccessFile bude ispravno zatvoren nakon završetka operacije.
Apache klasa FileUtils
Klasa FileUtils iz biblioteke Apache Commons IO pruža zgodnije metode za rad sa fajlovima ili direktorijumima.
1) Kopiranje fajla ili direktorijuma:
File srcFile = new File("path/to/src/file");
File destFile = new File("path/to/dest/file");
// Kopiraj fajl
FileUtils.copyFile(srcFile, destFile);
// Kopiraj direktorijum
FileUtils.copyDirectory(srcFile, destFile);2) Brisanje fajla ili direktorijuma:
File file = new File("path/to/file");
// Obriši fajl ili direktorijum
FileUtils.delete(file);Imajte na umu da, ako želite da obrišete neprazan direktorijum, prvo morate obrisati sve fajlove i poddirektorijume u njemu.
3) Pomeranje fajla ili direktorijuma:
File srcFile = new File("path/to/src/file");
File destFile = new File("path/to/dest/file");
// Pomeri fajl ili direktorijum
FileUtils.moveFile(srcFile, destFile);4) Dohvatanje informacija o fajlu ili direktorijumu:
File file = new File("path/to/file");
// Dohvati vreme poslednje izmene fajla ili direktorijuma
Date modifyTime = FileUtils.lastModified(file);
// Dohvati veličinu fajla ili direktorijuma
long size = FileUtils.sizeOf(file);
// Dohvati ekstenziju fajla ili direktorijuma
String extension = FileUtils.getExtension(file.getName());Hutool klasa FileUtil
Klasa FileUtil je utility klasa za rad sa fajlovima iz Hutool paketa alata; pruža niz jednostavnih i lako upotrebljivih metoda koje Java programerima pomažu da brzo završe zadatke vezane za fajlove.
Klasa FileUtil sadrži sledeće kategorije alatki:
- Operacije nad fajlovima: uključuju kreiranje, brisanje, kopiranje, pomeranje i preimenovanje fajlova i direktorijuma.
- Provere nad fajlovima: da li su fajl ili direktorijum neprazni, da li je direktorijum, da li je fajl itd.
- Apsolutne putanje: pretvaranje fajlova iz ClassPath-a u fajlove sa apsolutnom putanjom.
- Imena fajlova: dohvatanje glavnog imena fajla i ekstenzije.
- Operacije čitanja: uključuju getReader, readXXX operacije.
- Operacije pisanja: uključuju getWriter, writeXXX operacije.
Ispod su neke od uobičajenih metoda klase FileUtil:
- copyFile: kopira fajl. Ova metoda može kopirati zadati izvorni fajl u zadati odredišni fajl.
File dest = FileUtil.file("FileUtilDemo2.java");
FileUtil.copyFile(file, dest);- move: pomera fajl ili direktorijum. Ova metoda može pomeriti zadati izvorni fajl ili direktorijum u zadati odredišni fajl ili direktorijum.
FileUtil.move(file, dest, true);- del: briše fajl ili direktorijum. Ova metoda može obrisati zadati fajl ili direktorijum; ako zadati fajl ili direktorijum ne postoji, biće bačen izuzetak.
FileUtil.del(file);- rename: preimenuje fajl ili direktorijum. Ova metoda može preimenovati zadati fajl ili direktorijum u zadato novo ime.
FileUtil.rename(file, "FileUtilDemo3.java", true);- readLines: čita svaki red podatka iz fajla.
FileUtil.readLines(file, "UTF-8").forEach(System.out::println);Za više metoda pogledajte izvorni kod hutool-a — sadrži veoma mnogo korisnih metoda; samo čitanje će vam svakako podići nivo programiranja.
