Setiap panggilan read() atau write() pada stream berkas yang tidak dibuffer menempuh satu perjalanan penuh dari JVM ke kernel sistem operasi — sebuah system call yang relatif mahal. Membaca berkas seribu baris tanpa buffer berarti seribu system call, padahal kernel sanggup mengantarkan ribuan byte sekaligus dalam satu perjalanan. Dekorator BufferedReader dan BufferedWriter mengatasi ketidakefisienan itu dengan menyisipkan penampung memori di antara program dan stream: data ditukar dalam potongan besar, lalu program mengambil potongan kecil dari penampung yang sudah berada di memori. Pada Chapter ini kita praktikkan pembacaan baris-per-baris, penulisan dengan newLine, dan konsep flush yang tidak boleh disalahpahami.

Mengapa Buffering Mempercepat #

BufferedReader membungkus Reader lain dan menjaga array karakter berukuran default 8192. Saat program memanggil readLine(), dekorator memeriksa penampungnya; bila kosong, dekorator meminta Reader bawahannya mengisi penampung penuh sekaligus — satu system call untuk 8192 karakter — lalu menyajikan baris dari stok di memori. Efeknya, jumlah perjalanan ke kernel turun dari “sekali per baris” menjadi “sekali per 8192 karakter”. Prinsip yang sama berlaku untuk BufferedInputStream/BufferedOutputStream di sisi byte, dan untuk BufferedWriter yang menampung keluaran lalu menuliskannya sekaligus.

Berkas pendamping inventori.csv berisi catatan gudang tujuh baris: satu komentar berawalan #, satu baris judul kolom, dan lima baris data. Program berikut meringkasnya baris demi baris:

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class BacaInventori {
    public static void main(String[] args) throws IOException {
        Path sumber = Path.of("inventori.csv");
        int jenis = 0;
        long totalStok = 0;

        try (BufferedReader pembaca = Files.newBufferedReader(sumber, StandardCharsets.UTF_8)) {
            String catatan = pembaca.readLine(); // baris komentar, dibuang
            String judul = pembaca.readLine();   // baris nama kolom
            System.out.println("kolom         : " + judul);

            String baris;
            while ((baris = pembaca.readLine()) != null) {
                String[] kolom = baris.split(",");
                jenis++;
                totalStok += Long.parseLong(kolom[2]);
            }
        }
        System.out.println("jenis barang : " + jenis);
        System.out.println("total stok   : " + totalStok);
    }
}

Output yang Dihasilkan:

text
kolom         : kode,nama,jumlah
jenis barang : 5
total stok   : 605

Mari kita bedah alur pembacaannya:

  1. Baris 11-12 (Files.newBufferedReader): factory Files membuka berkas dengan charset UTF-8 dan langsung membungkusnya dalam BufferedReader — cara tersingkat mendapatkan pembaca baris yang sekaligus ter-buffer.
  2. Baris 16-18 (dua readLine() pertama): method ini mengembalikan satu baris tanpa tanda baris baru, atau null saat stream berakhir. Dua baris pembuka dibaca eksplisit: komentar dibuang, judul kolom dicetak.
  3. Baris 20-25 (while ((baris = pembaca.readLine()) != null)): pola baku memproses seluruh sisa baris. Setiap baris dipecah dengan split(","), kolom ketiga diurai menjadi long, lalu diakumulasi ke totalStok.
  4. Baris 28-29: kelima baris data menghasilkan lima jenis barang dan total stok 605 — angka yang bisa dihitung ulang dari isi inventori.csv sebagai pembanding.

Di sisi penulisan, buffer membawa konsekuensi baru: data yang baru ditulis belum tentu sampai ke berkas — data itu menunggu di penampung memori. Method flush() memaksa isi penampung dituliskan sekarang juga ke stream bawahannya. Method close() selalu mem-flush lebih dulu, sehingga pada try-with-resources yang ditutup rapi, flush() eksplisit tidak wajib; pemanggilan manual berguna ketika pembaca lain — proses lain, thread lain, atau terminal pengguna — harus melihat data sebelum penulis selesai. Untuk pemisah baris, newLine() memilih pemisah milik platform berjalan, bukan \n mentah.

Program berikut menulis laporan tiga baris dengan flush() di tengah, lalu membacanya kembali lewat jalur yang sering dipakai untuk sumber byte non-berkas:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class TulisLaporan {
    public static void main(String[] args) throws IOException {
        Path laporan = Path.of("laporan.txt");

        try (BufferedWriter penulis = Files.newBufferedWriter(laporan, StandardCharsets.UTF_8)) {
            penulis.write("Laporan Stok Gudang");
            penulis.newLine();
            penulis.write("Baris kedua tanpa baris baru");
            penulis.flush(); // dorong ke disk sekarang juga
            System.out.println("flush dipanggil : true");
            penulis.newLine();
            penulis.write("Akhir laporan");
        }

        // BufferedReader dari InputStream + charset eksplisit.
        try (BufferedReader pembaca = new BufferedReader(new InputStreamReader(
                new FileInputStream("laporan.txt"), StandardCharsets.UTF_8))) {
            List<String> baris = pembaca.lines().toList();
            System.out.println("jumlah baris    : " + baris.size());
            System.out.println("baris pertama   : " + baris.get(0));
            System.out.println("baris terakhir  : " + baris.get(baris.size() - 1));
        }
    }
}

Output yang Dihasilkan:

text
flush dipanggil : true
jumlah baris    : 3
baris pertama   : Laporan Stok Gudang
baris terakhir  : Akhir laporan

Perhatikan tiga perilaku dari keluaran dan strukturnya:

  1. Baris 15-26 (write, newLine, flush): teks ditulis tanpa pemisah; newLine() menambahkan pemisah sesuai platform. Panggilan flush() di tengah memaksa dua baris pertama turun ke disk walau penulis masih dipakai — berkas sementara berisi dua baris utuh di momen itu.
  2. Baris 27 (akhir try): close() otomatis mem-flush sisa penampung sehingga baris “Akhir laporan” ikut tersimpan tanpa flush() manual.
  3. Baris 30-36 (InputStreamReader + BufferedReader): jalur pembaca yang tidak lewat Files — FileInputStream memberi byte, InputStreamReader menerjemahkan dengan charset eksplisit, BufferedReader menambah buffer dan lines(). Komposisi tiga lapis ini adalah pola standar saat sumber byte berasal dari jaringan, System.in, atau stream lain yang tidak punya factory Files.

Dengan buffering, baca-tulis teks kita sudah efisien. Terkadang data yang ditulis bukan teks melainkan kondisi objek program itu sendiri — dan Java menyediakan mekanisme serialization untuk mengubah object graph menjadi byte dan memulihkannya kembali. Chapter berikutnya membahas ObjectOutputStream, interface Serializable, serta peran transient dan serialVersionUID.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

BacaInventori.java

Unduh berkas
java
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class BacaInventori {
    public static void main(String[] args) throws IOException {
        Path sumber = Path.of("inventori.csv");
        int jenis = 0;
        long totalStok = 0;

        try (BufferedReader pembaca = Files.newBufferedReader(sumber, StandardCharsets.UTF_8)) {
            String catatan = pembaca.readLine(); // baris komentar, dibuang
            String judul = pembaca.readLine();   // baris nama kolom
            System.out.println("kolom         : " + judul);

            String baris;
            while ((baris = pembaca.readLine()) != null) {
                String[] kolom = baris.split(",");
                jenis++;
                totalStok += Long.parseLong(kolom[2]);
            }
        }
        System.out.println("jenis barang : " + jenis);
        System.out.println("total stok   : " + totalStok);
    }
}

TulisLaporan.java

Unduh berkas
java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class TulisLaporan {
    public static void main(String[] args) throws IOException {
        Path laporan = Path.of("laporan.txt");

        try (BufferedWriter penulis = Files.newBufferedWriter(laporan, StandardCharsets.UTF_8)) {
            penulis.write("Laporan Stok Gudang");
            penulis.newLine();
            penulis.write("Baris kedua tanpa baris baru");
            penulis.flush(); // dorong ke disk sekarang juga
            System.out.println("flush dipanggil : true");
            penulis.newLine();
            penulis.write("Akhir laporan");
        }

        // BufferedReader dari InputStream + charset eksplisit.
        try (BufferedReader pembaca = new BufferedReader(new InputStreamReader(
                new FileInputStream("laporan.txt"), StandardCharsets.UTF_8))) {
            List<String> baris = pembaca.lines().toList();
            System.out.println("jumlah baris    : " + baris.size());
            System.out.println("baris pertama   : " + baris.get(0));
            System.out.println("baris terakhir  : " + baris.get(baris.size() - 1));
        }
    }
}