Aplikasi nyata jarang berurusan dengan satu berkas — biasanya satu pohon: folder proyek yang perlu dipindai berkas .java-nya, direktori unggahan yang isinya harus diurutkan, atau arsip yang strukturnya perlu didaftar. java.nio.file menyediakan tiga alat penjelajahan dengan karakter berbeda: Files.list() untuk satu tingkat, DirectoryStream untuk iterasi hemat dengan filter, dan Files.walk() untuk menuruni seluruh pohon secara rekursif. Ditambah PathMatcher yang mengenali pola glob dan regex, kit penjelajahan ini menuntaskan hampir semua kebutuhan pemindaian. Pada Chapter ini kita membangun pohon direktori contoh, lalu menjelajahinya dengan ketiga alat tersebut.
List, DirectoryStream, dan Walk #
Files.list(dir) membuka satu tingkat isi direktori sebagai Stream<Path>; hasilnya serupa dengan File.list() era lama, tetapi berupa Path lengkap dan bisa langsung difilter atau dipetakan. Files.walk(mulai) turun lebih jauh: stream yang dihasilkannya memuat direktori awal itu sendiri, seluruh anak, cucu, dan seterusnya — traversal depth-first yang oleh default tidak mengikuti symbolic link demi keamanan dari perulangan tanpa dasar. Kedalaman bisa dibatasi dengan argumen kedua: walk(proyek, 1) berhenti setelah satu tingkat di bawah titik awal. Adapun DirectoryStream adalah interface iteratif lawas NIO.2 — bukan Stream — yang tetap andal untuk perulangan for klasik dan menerima glob langsung di constructor-nya.
Program berikut membangun pohon contoh proyek beserta isinya, lalu menjelajahinya tiga cara:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class PohonProyek {
public static void main(String[] args) throws IOException {
Path proyek = Path.of("proyek");
// Bangun struktur contoh: proyek/{README.md, catatan.txt, src/{main,test}}
Files.createDirectories(proyek.resolve("src/main"));
Files.createDirectories(proyek.resolve("src/test"));
Files.writeString(proyek.resolve("README.md"), "# Proyek contoh\n");
Files.writeString(proyek.resolve("catatan.txt"), "catatan pengembang\n");
Files.writeString(proyek.resolve("src/main/Aplikasi.java"), "class Aplikasi {}\n");
Files.writeString(proyek.resolve("src/main/Util.java"), "class Util {}\n");
Files.writeString(proyek.resolve("src/test/AplikasiTest.java"), "class AplikasiTest {}\n");
try (Stream<Path> semua = Files.walk(proyek)) {
System.out.println("-- walk seluruh kedalaman --");
semua.sorted().map(Path::toString).forEach(p -> System.out.println(" " + p));
}
try (Stream<Path> dangkal = Files.walk(proyek, 1)) {
System.out.println("-- walk kedalaman 1 --");
dangkal.sorted().map(Path::toString).forEach(p -> System.out.println(" " + p));
}
try (Stream<Path> isi = Files.list(proyek.resolve("src"))) {
System.out.println("-- list src --");
isi.sorted().map(p -> p.getFileName().toString())
.forEach(p -> System.out.println(" " + p));
}
}
}Output yang Dihasilkan:
-- walk seluruh kedalaman --
proyek
proyek/README.md
proyek/catatan.txt
proyek/src
proyek/src/main
proyek/src/main/Aplikasi.java
proyek/src/main/Util.java
proyek/src/test
proyek/src/test/AplikasiTest.java
-- walk kedalaman 1 --
proyek
proyek/README.md
proyek/catatan.txt
proyek/src
-- list src --
main
testTiga keputusan pada program tersebut menentukan mutu keluarannya:
- Baris 9-16 (pembangunan pohon): struktur dibuat oleh program itu sendiri lewat
createDirectoriesdanwriteString, sehingga isi pohon selalu identik setiap eksekusi — syarat agar hasil pemindaian bisa dibandingkan. sorted()di setiap stream: urutan bawaan sistem berkas tidak dijamin konsisten antar mesin maupun antar eksekusi. Pengurutan eksplisit menjadikan keluaran deterministik —PathmengimplementasikanComparableberdasarkan elemen path.walktanpa batas vswalk(proyek, 1): keluaran pertama memuat sembilan entri dariproyekhingga berkas di kedalaman tiga; keluaran kedua berhenti pada empat entri — titik awal ditambah anak langsungnya.Files.listkemudian menampilkan isisrcsaja: dua direktori tanpa berkas di dalamnya.
Menyaring dengan Glob dan Regex #
Pola glob — sintaks berwildcard yang dipakai shell — cocok untuk pencocokan nama berkas: *.java berarti semua nama berakhiran demikian, *.{txt,md} berarti beberapa akhiran sekaligus, tanda ? mewakili satu karakter, dan ** melintasi pemisah direktori. PathMatcher dari FileSystems.getDefault().getPathMatcher() mengkompilasi pola dengan awalan "glob:" atau "regex:"; matcher kemudian menguji sebuah Path lewat matches(). Karena glob * dan ? tidak menembus batas direktori, kombinasi glob:**/*.java dengan Files.walk menjadi resep standar “temukan semua berkas Java di pohon ini”.
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class CariPola {
public static void main(String[] args) throws IOException {
Path proyek = Path.of("proyek");
// Struktur contoh dibuat ulang agar program berdiri sendiri.
Files.createDirectories(proyek.resolve("src/main"));
Files.createDirectories(proyek.resolve("src/test"));
Files.writeString(proyek.resolve("README.md"), "# Proyek contoh\n");
Files.writeString(proyek.resolve("catatan.txt"), "catatan pengembang\n");
Files.writeString(proyek.resolve("src/main/Aplikasi.java"), "class Aplikasi {}\n");
Files.writeString(proyek.resolve("src/main/Util.java"), "class Util {}\n");
Files.writeString(proyek.resolve("src/test/AplikasiTest.java"), "class AplikasiTest {}\n");
// DirectoryStream dengan pola glob: satu tingkat saja.
List<String> dokumen = new ArrayList<>();
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(proyek, "*.{txt,md}")) {
for (Path p : stream) {
dokumen.add(p.getFileName().toString());
}
}
dokumen.sort(null);
System.out.println("glob txt/md : " + dokumen);
// PathMatcher glob menembus kedalaman bila dipadukan dengan walk.
PathMatcher cocokJava =
FileSystems.getDefault().getPathMatcher("glob:**/*.java");
List<String> berkasJava;
try (Stream<Path> semua = Files.walk(proyek)) {
berkasJava = semua.filter(cocokJava::matches)
.map(Path::toString)
.sorted()
.toList();
}
System.out.println("glob **/*.java: " + berkasJava);
// PathMatcher berbasis regex.
PathMatcher diSrc =
FileSystems.getDefault().getPathMatcher("regex:proyek/src/.*");
long jumlah;
try (Stream<Path> semua = Files.walk(proyek)) {
jumlah = semua.filter(diSrc::matches).count();
}
System.out.println("regex src/ : " + jumlah + " entri");
}
}Output yang Dihasilkan:
glob txt/md : [README.md, catatan.txt]
glob **/*.java: [proyek/src/main/Aplikasi.java, proyek/src/main/Util.java, proyek/src/test/AplikasiTest.java]
regex src/ : 5 entriTiga penyaringan pada program tersebut menunjukkan spektrum lengkap:
newDirectoryStream(proyek, "*.{txt,md}"): glob dijalankan oleh sistem berkas atau JVM saat iterasi berlangsung; hasilnya hanyaREADME.mddancatatan.txtkarenasrctidak berakhiran teks yang diminta — direktori pun boleh lolos pola bila cocok.glob:**/*.java+walk: pola ber-prefix**/cocok di kedalaman mana pun, sehingga ketiga berkas Java darisrc/maindansrc/testterkumpul; tanpa**/, hanya berkas tepat di bawah titik awal yang lolos.regex:proyek/src/.*: pola regex memeriksa seluruh string path —proyek/srcsendiri tidak lolos (tidak ada pemisah trailing), sedangkan lima entri di bawahnya lolos: dua subdirektorimaindantestbeserta isinya. Regex memberi kendali penuh namun menuntut kehati-hatian dengan pemisah path yang berbeda antar platform.
Pemindaian berkala seperti ini memang berguna, tetapi program juga perlu bereaksi saat berkas berubah — tanpa terus-menerus memindai folder yang sama. Chapter berikutnya membahas WatchService: layanan sistem operasi yang mengirim event ke Java begitu direktori yang dipantau berubah.
Referensi #
- Oracle: Class Files (Java SE 25 API)
- Oracle: Interface DirectoryStream (Java SE 25 API)
- Oracle: Class FileSystem getPathMatcher (Java SE 25 API)
- dev.java: Java I/O
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
CariPola.java
Unduh berkasimport java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class CariPola {
public static void main(String[] args) throws IOException {
Path proyek = Path.of("proyek");
// Struktur contoh dibuat ulang agar program berdiri sendiri.
Files.createDirectories(proyek.resolve("src/main"));
Files.createDirectories(proyek.resolve("src/test"));
Files.writeString(proyek.resolve("README.md"), "# Proyek contoh\n");
Files.writeString(proyek.resolve("catatan.txt"), "catatan pengembang\n");
Files.writeString(proyek.resolve("src/main/Aplikasi.java"), "class Aplikasi {}\n");
Files.writeString(proyek.resolve("src/main/Util.java"), "class Util {}\n");
Files.writeString(proyek.resolve("src/test/AplikasiTest.java"), "class AplikasiTest {}\n");
// DirectoryStream dengan pola glob: satu tingkat saja.
List<String> dokumen = new ArrayList<>();
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(proyek, "*.{txt,md}")) {
for (Path p : stream) {
dokumen.add(p.getFileName().toString());
}
}
dokumen.sort(null);
System.out.println("glob txt/md : " + dokumen);
// PathMatcher glob menembus kedalaman bila dipadukan dengan walk.
PathMatcher cocokJava =
FileSystems.getDefault().getPathMatcher("glob:**/*.java");
List<String> berkasJava;
try (Stream<Path> semua = Files.walk(proyek)) {
berkasJava = semua.filter(cocokJava::matches)
.map(Path::toString)
.sorted()
.toList();
}
System.out.println("glob **/*.java: " + berkasJava);
// PathMatcher berbasis regex.
PathMatcher diSrc =
FileSystems.getDefault().getPathMatcher("regex:proyek/src/.*");
long jumlah;
try (Stream<Path> semua = Files.walk(proyek)) {
jumlah = semua.filter(diSrc::matches).count();
}
System.out.println("regex src/ : " + jumlah + " entri");
}
}
PohonProyek.java
Unduh berkasimport java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class PohonProyek {
public static void main(String[] args) throws IOException {
Path proyek = Path.of("proyek");
// Bangun struktur contoh: proyek/{README.md, catatan.txt, src/{main,test}}
Files.createDirectories(proyek.resolve("src/main"));
Files.createDirectories(proyek.resolve("src/test"));
Files.writeString(proyek.resolve("README.md"), "# Proyek contoh\n");
Files.writeString(proyek.resolve("catatan.txt"), "catatan pengembang\n");
Files.writeString(proyek.resolve("src/main/Aplikasi.java"), "class Aplikasi {}\n");
Files.writeString(proyek.resolve("src/main/Util.java"), "class Util {}\n");
Files.writeString(proyek.resolve("src/test/AplikasiTest.java"), "class AplikasiTest {}\n");
try (Stream<Path> semua = Files.walk(proyek)) {
System.out.println("-- walk seluruh kedalaman --");
semua.sorted().map(Path::toString).forEach(p -> System.out.println(" " + p));
}
try (Stream<Path> dangkal = Files.walk(proyek, 1)) {
System.out.println("-- walk kedalaman 1 --");
dangkal.sorted().map(Path::toString).forEach(p -> System.out.println(" " + p));
}
try (Stream<Path> isi = Files.list(proyek.resolve("src"))) {
System.out.println("-- list src --");
isi.sorted().map(p -> p.getFileName().toString())
.forEach(p -> System.out.println(" " + p));
}
}
}