Selama Section ini kita menguasai satu per satu alat I/O Java — stream, reader, buffer, channel, atribut, izin. Kode nyata jarang semudah contoh: berkas input bisa hilang atau berisi baris rusak, proses bisa terputus di tengah penulisan, dan hasil setengah jadi lebih buruk daripada tidak ada hasil sama sekali. Pemrosesan berkas yang tangguh adalah seni merangkai alat-alat itu menjadi alur yang tetap terhormat saat semuanya berjalan buruk: charset dinyatakan eksplisit, sumber daya ditutup pasti, exception yang spesifik ditangani dengan sengaja, input divalidasi sebelum dipercaya, dan hasil dipasang secara atomik lalu diverifikasi. Chapter penutup ini merangkai seluruhnya dalam satu pipeline lengkap.
Lima Prinsip Kode I/O yang Tangguh #
Sebelum melihat kode, mari tetapkan sasaran desainnya dalam lima prinsip. Pertama, charset eksplisit di setiap baca-tulis teks — StandardCharsets.UTF_8 ditulis di kode, bukan diwariskan ke keberuntungan locale. Kedua, try-with-resources untuk setiap sumber daya; stream dan reader yang bocor bukan hanya boros — pada Windows bisa mengunci berkas dari proses lain. Ketiga, tangkap exception yang paling spesifik lebih dulu: NoSuchFileException menuntaskan “input hilang”, NumberFormatException menuntaskan “data rusak”, dan IOException umum menjadi jaring terakhir — setiap jenis digiring ke penanganan yang berbeda. Keempat, validasi sebelum percaya: baris yang tidak lolos pemeriksaan bentuk dikumpulkan terpisah, bukan dibiarkan meracuni hasil. Kelima, pasang hasil secara atomik: tulis ke berkas sementara di direktori yang sama, lalu Files.move dengan ATOMIC_MOVE — pembaca tidak pernah melihat berkas hasil yang setengah tertulis.
Studi Kasus: Pipeline Ringkasan Peserta #
Berkas pendamping peserta.csv sengaja memuat dua baris bermasalah: Dewi tanpa kolom skor, dan Ani,seratus dengan skor bukan angka. Pipeline kita membaca sumber dengan charset eksplisit, memilah baris valid dari yang ditolak, menulis hasil ke ringkasan.tmp, memasangnya sebagai ringkasan.csv secara atomik, lalu membaca balik untuk verifikasi. Konfigurasi ambang kelulusan dibaca dari konfigurasi.txt yang tidak tersedia — hilangnya berkas opsional harusnya tidak menghentikan pemrosesan:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
public class ProsesPeserta {
public static void main(String[] args) {
Path sumber = Path.of("peserta.csv");
Path hasil = Path.of("ringkasan.csv");
Path sementara = Path.of("ringkasan.tmp");
int batas = bacaBatas();
List<String> valid = new ArrayList<>();
List<String> ditolak = new ArrayList<>();
try (BufferedReader pembaca =
Files.newBufferedReader(sumber, StandardCharsets.UTF_8)) {
pembaca.readLine(); // buang baris judul kolom
String baris;
while ((baris = pembaca.readLine()) != null) {
if (baris.isBlank()) {
continue; // baris kosong diabaikan diam-diam
}
String[] kolom = baris.split(",");
try {
int skor = Integer.parseInt(kolom[1].trim());
if (skor < 0) {
ditolak.add(baris);
} else {
valid.add(baris);
}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
ditolak.add(baris);
}
}
} catch (NoSuchFileException e) {
System.out.println("sumber hilang : " + e.getFile());
return;
} catch (IOException e) {
System.out.println("baca gagal : " + e.getClass().getSimpleName());
return;
}
System.out.println("konfigurasi : batas=" + batas);
System.out.println("valid : " + valid.size());
System.out.println("ditolak : " + ditolak.size());
try (BufferedWriter penulis =
Files.newBufferedWriter(sementara, StandardCharsets.UTF_8)) {
penulis.write("nama,score,hasil\n");
for (String baris : valid) {
String[] kolom = baris.split(",");
int skor = Integer.parseInt(kolom[1].trim());
String keputusan = skor >= batas ? "lulus" : "ulang";
penulis.write(kolom[0] + "," + skor + "," + keputusan + "\n");
}
} catch (IOException e) {
System.out.println("tulis gagal : " + e.getClass().getSimpleName());
return;
}
// Pasang hasil secara atomik; jatuh ke cadangan bila tidak didukung.
try {
Files.move(sementara, hasil, StandardCopyOption.ATOMIC_MOVE);
System.out.println("pasang : ATOMIC_MOVE");
} catch (AtomicMoveNotSupportedException e) {
try {
Files.move(sementara, hasil, StandardCopyOption.REPLACE_EXISTING);
System.out.println("pasang : fallback REPLACE_EXISTING");
} catch (IOException e2) {
System.out.println("pasang gagal : " + e2.getClass().getSimpleName());
return;
}
} catch (IOException e) {
System.out.println("pasang gagal : " + e.getClass().getSimpleName());
return;
}
// Verifikasi akhir: baca balik hasil dan hitung barisnya.
try (BufferedReader pembaca =
Files.newBufferedReader(hasil, StandardCharsets.UTF_8)) {
System.out.println("-- ringkasan.csv --");
pembaca.lines().forEach(b -> System.out.println(" " + b));
long jumlah = Files.lines(hasil, StandardCharsets.UTF_8).count();
System.out.println("verifikasi : " + jumlah
+ " baris (1 judul + " + valid.size() + " data)");
} catch (IOException e) {
System.out.println("verifikasi gagal: " + e.getClass().getSimpleName());
}
}
// Konfigurasi opsional: hilang atau rusak tidak membatalkan proses.
static int bacaBatas() {
try {
String isi = Files.readString(Path.of("konfigurasi.txt"));
return Integer.parseInt(isi.trim());
} catch (IOException | NumberFormatException e) {
return 60;
}
}
}Output yang Dihasilkan:
konfigurasi : batas=60
valid : 3
ditolak : 2
pasang : ATOMIC_MOVE
-- ringkasan.csv --
nama,score,hasil
Budi,90,lulus
Siti,85,lulus
Rudi,78,lulus
verifikasi : 4 baris (1 judul + 3 data)Mari kita bedah keputusan-keputusan pentingnya:
- Baris 21 (
bacaBatas()lebih dulu): konfigurasi dibaca sebelum loop utama.bacaBatasmenangkapIOException(termasukNoSuchFileExceptionyang merupakan subclass-nya) danNumberFormatExceptiondalam satu multi-catch lalu mengembalikan nilai standar60— berkas opsional yang hilang atau isinya rusak tidak boleh membatalkan seluruh proses, dan keluaran membuktikannya lewatbatas=60. - Baris 24-46 (validasi berlapis): baris kosong diabaikan; setiap baris dipecah dan
Integer.parseIntdijaga try-catch kecil —NumberFormatExceptionuntukseratus,ArrayIndexOutOfBoundsExceptionuntuk baris tanpa kolom kedua. Keduanya berujung pada daftarditolakyang terpisah, sehingga data rusak tercatat, bukan menghentikan atau diselundupkan ke hasil. - Baris 47-53 (dua catch berjenjang):
NoSuchFileExceptionditangkap sebelumIOExceptionkarena lebih spesifik — compiler menolak urutan terbalik. Keduanya menghentikan program dengan pesan berbeda: hilangnya sumber adalah kondisi lain dari kegagalan baca di tengah jalan. - Baris 62-71 (tulis ke sementara): hasil ditulis ke
ringkasan.tmp, bukan langsung keringkasan.csv. Selama penulisan berlangsung, berkas hasil yang lama tetap utuh — pembaca lain tidak pernah melihatringkasan.csvyang setengah jadi. - Baris 73-89 (pasangan
ATOMIC_MOVE):Files.movedenganStandardCopyOption.ATOMIC_MOVEmenggantiringkasan.tmpmenjadiringkasan.csvdalam satu operasirenamekernel yang tidak bisa diam setengah jalan. Bila sistem berkas menolak —AtomicMoveNotSupportedException— fallbackREPLACE_EXISTINGtetap menjalankan tugas dengan jaminan lebih lemah; program mencatat jalur mana yang dipakai sehingga perilakunya terlihat. - Baris 91-102 (verifikasi baca-balik): hasil dibaca kembali dan jumlah barisnya dihitung; kecocokan “1 judul + 3 data” menutup siklus tulis-lalu-periksa. Bila proses ini dijalankan ulang, hasil yang sama tertulis kembali — alur idempoten yang tidak menumpuk efek samping.
Pulihkan Input yang Hilang dengan Elegan #
Satu pola penutup yang sering dipakai untuk berkas pengaturan: input hilang bukanlah error, melainkan ajak menciptakan sesuatu. Program berikut membaca pengaturan.txt; karena berkas itu belum ada, program menuliskan nilai bawaan dan melanjutkan seolah tidak terjadi apa-apa — dan pada eksekusi berikutnya berkas itu sudah ada:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
public class PulihkanInput {
public static void main(String[] args) throws IOException {
Path input = Path.of("pengaturan.txt");
String isi;
try {
isi = Files.readString(input, StandardCharsets.UTF_8);
System.out.println("sumber : berkas");
} catch (NoSuchFileException e) {
isi = "mode=wajar\n";
Files.writeString(input, isi, StandardCharsets.UTF_8);
System.out.println("sumber : bawaan, berkas dibuat");
}
System.out.println("isi : " + isi.lines().findFirst().orElse(""));
}
}Output yang Dihasilkan:
sumber : bawaan, berkas dibuat
isi : mode=wajarProgram pendek ini memperlihatkan pola self-healing yang sehat: NoSuchFileException ditangkap tepat untuk satu tujuan — mengisi nilai bawaan — lalu eksekusi berlanjut ke jalur yang sama dengan kasus sukses. Tidak ada return mendadak, tidak ada stack trace ke terminal pengguna, dan berkas yang tercipta membuat eksekusi berikutnya memasuki cabang “berkas” secara alami.
Dengan ini perjalanan kita di dunia I/O Java tuntas: byte mentah, karakter dan charset, buffer dan channel, metadata, izin, pemantauan perubahan, hingga pipeline transaksional. Semua contoh sejauh ini berjalan satu utas — padahal I/O justru wilayah yang paling diuntungkan eksekusi paralel: menunggu disk dan jaringan adalah waktu luang yang bisa dipakai mengerjakan yang lain. Section berikutnya, Concurrency dan Multithreading, membuka kotak perkakas tersebut: thread, ExecutorService, hingga virtual threads untuk I/O yang masif.
Referensi #
- Oracle: Class Files (Java SE 25 API)
- Oracle: Enum StandardCopyOption (Java SE 25 API)
- dev.java: Java I/O
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ProsesPeserta.java
Unduh berkasimport java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
public class ProsesPeserta {
public static void main(String[] args) {
Path sumber = Path.of("peserta.csv");
Path hasil = Path.of("ringkasan.csv");
Path sementara = Path.of("ringkasan.tmp");
int batas = bacaBatas();
List<String> valid = new ArrayList<>();
List<String> ditolak = new ArrayList<>();
try (BufferedReader pembaca =
Files.newBufferedReader(sumber, StandardCharsets.UTF_8)) {
pembaca.readLine(); // buang baris judul kolom
String baris;
while ((baris = pembaca.readLine()) != null) {
if (baris.isBlank()) {
continue; // baris kosong diabaikan diam-diam
}
String[] kolom = baris.split(",");
try {
int skor = Integer.parseInt(kolom[1].trim());
if (skor < 0) {
ditolak.add(baris);
} else {
valid.add(baris);
}
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
ditolak.add(baris);
}
}
} catch (NoSuchFileException e) {
System.out.println("sumber hilang : " + e.getFile());
return;
} catch (IOException e) {
System.out.println("baca gagal : " + e.getClass().getSimpleName());
return;
}
System.out.println("konfigurasi : batas=" + batas);
System.out.println("valid : " + valid.size());
System.out.println("ditolak : " + ditolak.size());
try (BufferedWriter penulis =
Files.newBufferedWriter(sementara, StandardCharsets.UTF_8)) {
penulis.write("nama,score,hasil\n");
for (String baris : valid) {
String[] kolom = baris.split(",");
int skor = Integer.parseInt(kolom[1].trim());
String keputusan = skor >= batas ? "lulus" : "ulang";
penulis.write(kolom[0] + "," + skor + "," + keputusan + "\n");
}
} catch (IOException e) {
System.out.println("tulis gagal : " + e.getClass().getSimpleName());
return;
}
// Pasang hasil secara atomik; jatuh ke cadangan bila tidak didukung.
try {
Files.move(sementara, hasil, StandardCopyOption.ATOMIC_MOVE);
System.out.println("pasang : ATOMIC_MOVE");
} catch (AtomicMoveNotSupportedException e) {
try {
Files.move(sementara, hasil, StandardCopyOption.REPLACE_EXISTING);
System.out.println("pasang : fallback REPLACE_EXISTING");
} catch (IOException e2) {
System.out.println("pasang gagal : " + e2.getClass().getSimpleName());
return;
}
} catch (IOException e) {
System.out.println("pasang gagal : " + e.getClass().getSimpleName());
return;
}
// Verifikasi akhir: baca balik hasil dan hitung barisnya.
try (BufferedReader pembaca =
Files.newBufferedReader(hasil, StandardCharsets.UTF_8)) {
System.out.println("-- ringkasan.csv --");
pembaca.lines().forEach(b -> System.out.println(" " + b));
long jumlah = Files.lines(hasil, StandardCharsets.UTF_8).count();
System.out.println("verifikasi : " + jumlah
+ " baris (1 judul + " + valid.size() + " data)");
} catch (IOException e) {
System.out.println("verifikasi gagal: " + e.getClass().getSimpleName());
}
}
// Konfigurasi opsional: hilang atau rusak tidak membatalkan proses.
static int bacaBatas() {
try {
String isi = Files.readString(Path.of("konfigurasi.txt"));
return Integer.parseInt(isi.trim());
} catch (IOException | NumberFormatException e) {
return 60;
}
}
}
PulihkanInput.java
Unduh berkasimport java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
public class PulihkanInput {
public static void main(String[] args) throws IOException {
Path input = Path.of("pengaturan.txt");
String isi;
try {
isi = Files.readString(input, StandardCharsets.UTF_8);
System.out.println("sumber : berkas");
} catch (NoSuchFileException e) {
isi = "mode=wajar\n";
Files.writeString(input, isi, StandardCharsets.UTF_8);
System.out.println("sumber : bawaan, berkas dibuat");
}
System.out.println("isi : " + isi.lines().findFirst().orElse(""));
}
}