Cara paling naif untuk mengetahui perubahan isi folder adalah memindainya berulang kali — mencatat isi, menunggu, membandingkan, dan seterusnya. Pendekatan itu membakar CPU dan tetap melewatkan perubahan yang terjadi di sela dua pemindaian. Sistem operasi modern punya mekanisme yang jauh lebih baik: kernel Linux mengelola inotify, layanan yang mencatat event berkas begitu terjadi, dan Java mengeksposnya melalui java.nio.file.WatchService. Program mendaftarkan direktori yang ingin dipantau, lalu tidur tanpa beban sampai event datang — pembacaan berkas konfigurasi yang baru tersimpan, reaksi terhadap unggahan, atau sinkronisasi folder. Pada Chapter ini kita membangun pemantau lengkap dengan WatchService, memahami siklus WatchKey, dan menulis pengujian yang deterministik.
Mendaftarkan Direktori dan Membaca Event #
Alur kerjanya berpusat pada tiga aktor. WatchService adalah antrean pusat milik JVM; sebuah direktori didaftarkan lewat Path.register(...) dengan daftar WatchEvent.Kind yang diminati — ENTRY_CREATE saat entri baru muncul, ENTRY_MODIFY saat isi berubah, ENTRY_DELETE saat entri hilang. Pendaftaran mengembalikan sebuah WatchKey; menurut Javadoc-nya, key inilah token yang mewakili pendaftaran dan berada dalam keadaan ready setiap kali event untuk direktorinya terkumpul. Method pollEvents() pada key mengosongkan event yang terkumpul — e.kind() memberi jenis perubahannya dan e.context() memberi nama Path relatif terhadap direktori yang dipantau. Terakhir, reset() mengembalikan key ke keadaan siap; bila reset() mengembalikan false, direktori yang dipantau sudah tidak lagi tersedia dan key batal.
Tiga cara menunggu event tersedia, dengan tingkat kesabaran berbeda: poll() kembali langsung dengan null bila antrean kosong; poll(timeout, unit) menunggu paling lama durasi tertentu; take() menunggu tanpa batas sampai event tiba — cocok untuk thread khusus pemantau di aplikasi server.
Untuk pengujian yang dapat diulang, program berikut memantau direktori sementara lalu memicu perubahan sendiri — menulis, menulis ulang, dan menghapus satu berkas — sehingga event pasti terjadi tanpa bergantung pada kecepatan mesin:
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
public class PantauDirektori {
public static void main(String[] args)
throws IOException, InterruptedException {
Path dir = Files.createTempDirectory("pantau");
TreeSet<String> riwayat = new TreeSet<>();
try (WatchService layanan = FileSystems.getDefault().newWatchService()) {
WatchKey kunci = dir.register(layanan,
ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
// Perubahan dipicu sendiri agar event pasti muncul.
Path catatan = dir.resolve("catatan.txt");
Files.writeString(catatan, "baris pertama");
Files.writeString(catatan, "baris pertama\nditambah");
Files.delete(catatan);
WatchKey terima;
while ((terima = layanan.poll(1, TimeUnit.SECONDS)) != null) {
for (WatchEvent<?> e : terima.pollEvents()) {
riwayat.add(e.kind() + ": " + e.context());
}
if (!terima.reset()) {
break;
}
}
System.out.println("kunci valid : " + kunci.isValid());
}
for (String p : riwayat) {
System.out.println("peristiwa : " + p);
}
Files.delete(dir);
}
}Output yang Dihasilkan:
kunci valid : true
peristiwa : ENTRY_CREATE: catatan.txt
peristiwa : ENTRY_DELETE: catatan.txt
peristiwa : ENTRY_MODIFY: catatan.txtMari kita bedah kerja sama tiap baris pentingnya:
- Baris 17 (
Files.createTempDirectory("pantau")): direktori uji unik dibuat di lokasi sementara sistem operasi; namanya memang acak, tetapi program hanya mencetak nama berkas dalam event, bukan nama direktorinya, sehingga keluaran tetap stabil. - Baris 21-22 (
register): direktori didaftarkan untuk tiga jenis event sekaligus; pendaftaran hanya berlaku satu tingkat — berkas di subdirektori tidak terpantau kecuali subdirektori itu sendiri juga didaftarkan. - Baris 25-28 (tiga aksi): menulis berkas memicu
ENTRY_CREATE, penulisan kedua memicuENTRY_MODIFY, dan penghapusan memicuENTRY_DELETE. Ketiganya terjadi setelah pendaftaran sehingga kernel pasti merekamnya. - Baris 30-37 (
poll(1, TimeUnit.SECONDS)): loop menguras antrean bergiliran; ketika tidak ada lagi event selama satu detik,pollmengembalikannulldan perulangan berhenti. Event dikumpulkan keTreeSetsehingga tercetak terurut dan ganda tidak dobel. - Baris 38 (
kunci.isValid()): key masih sah karena direktorinya masih ada saat diperiksa;reset()di baris 35 berhasil mengembalikan keadaan siap.Files.delete(dir)di baris terakhir membersihkan sisa uji. try (WatchService ...):WatchServicemengimplementasikanCloseable; penutupan otomatis melepaskan seluruh sumber daya kernel yang dipakai semua key terdaftar.
Perlu dicatat bahwa keluaran menampilkan tiga jenis event untuk tiga aksi — tetapi urutan kedatangan event di antrean tidak dijamin oleh spesifikasi. Program yang benar seharusnya memperlakukan event sebagai petunjuk (“ada sesuatu berubah di sini”), lalu memeriksa kondisi berkas sesungguhnya sebelum bertindak — misalnya membaca ulang isi berkas setelah ENTRY_MODIFY, karena penulis lain bisa saja masih menyisakan penulisan setengah jalan.
Pemantauan pasif seperti ini biasanya berpasangan dengan berkas kerja sementara — data yang sedang diproses, unggahan yang belum terverifikasi, atau berkas antara proses panjang. Chapter berikutnya membahas Files.createTempFile dan createTempDirectory: cara membuat berkas sementara yang aman, menamainya dengan benar, dan terutama — membersihkannya kembali.
Referensi #
- Oracle: Interface WatchService (Java SE 25 API)
- Oracle: Interface WatchKey (Java SE 25 API)
- Oracle: Package java.nio.file (Java SE 25 API)
- dev.java: Java I/O
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
PantauDirektori.java
Unduh berkasimport java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
public class PantauDirektori {
public static void main(String[] args)
throws IOException, InterruptedException {
Path dir = Files.createTempDirectory("pantau");
TreeSet<String> riwayat = new TreeSet<>();
try (WatchService layanan = FileSystems.getDefault().newWatchService()) {
WatchKey kunci = dir.register(layanan,
ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
// Perubahan dipicu sendiri agar event pasti muncul.
Path catatan = dir.resolve("catatan.txt");
Files.writeString(catatan, "baris pertama");
Files.writeString(catatan, "baris pertama\nditambah");
Files.delete(catatan);
WatchKey terima;
while ((terima = layanan.poll(1, TimeUnit.SECONDS)) != null) {
for (WatchEvent<?> e : terima.pollEvents()) {
riwayat.add(e.kind() + ": " + e.context());
}
if (!terima.reset()) {
break;
}
}
System.out.println("kunci valid : " + kunci.isValid());
}
for (String p : riwayat) {
System.out.println("peristiwa : " + p);
}
Files.delete(dir);
}
}