Keluarga java.io bekerja dengan model aliran: data mengalir sekali lewat dan hilang begitu sudah terbaca. NIO memperkenalkan model yang berbeda — data berdiam dalam wadah yang bisa kita bolak-balik: membaca, memutar posisi, mengisi ulang. Wadah itu bernama Buffer, dan ByteBuffer adalah anggotanya yang paling sering dipakai karena menjadi mata uang resmi seluruh channel NIO. Memahami buffer berarti memahami tiga penanda yang mengendalikan isinya: position, limit, dan capacity. Pada Chapter ini kita membedah ketiganya sampai ke siklus put-flip-get yang menjadi denyut setiap program NIO.

Tiga Penanda: Position, Limit, Capacity #

Sebuah ByteBuffer adalah array byte berbalut kendali. capacity adalah jumlah byte yang bisa ditampung — tetap sejak buffer dibuat. position menunjuk indeks byte berikutnya yang akan dibaca atau ditulis. limit adalah pagar batas: indeks pertama yang tidak boleh disentuh. Saat menulis, kita mengisi dari position dan penanda bergerak maju; saat membaca, kita mengonsumsi dari position hingga limit. Perpindahan antara mode menulis dan mode membaca itulah tugas flip(), rewind(), dan clear() — bukan sekadar ritual, melainkan cara buffer mengingat berapa data nyata yang tersimpan.

Bentuk wadah berkapasitas 8 byte berisi tiga nilai dapat digambarkan begini:

text
        +----+----+----+----+----+----+----+----+
        | 10 | 20 | 30 |    |    |    |    |    |     capacity = 8
        +----+----+----+----+----+----+----+----+
          ↑                                     ↑
      position = 0                        limit = 8

    setelah flip():  position = 0,  limit = 3   (mode baca)

Program berikut menjalankan siklus penuh itu sambil mencatat ketiga penanda di setiap tahap:

import java.nio.ByteBuffer;

public class AnatomiBuffer {
    public static void main(String[] args) {
        ByteBuffer penampung = ByteBuffer.allocate(8);
        System.out.println("setelah allocate : pos=" + penampung.position()
                + " lim=" + penampung.limit()
                + " cap=" + penampung.capacity());

        penampung.put((byte) 10);
        penampung.put((byte) 20);
        penampung.put((byte) 30);
        System.out.println("setelah 3 put    : pos=" + penampung.position()
                + " lim=" + penampung.limit());

        penampung.flip();
        System.out.println("setelah flip     : pos=" + penampung.position()
                + " lim=" + penampung.limit());

        while (penampung.hasRemaining()) {
            System.out.println("terbaca          : " + penampung.get());
        }
        System.out.println("setelah get semua: pos=" + penampung.position()
                + " lim=" + penampung.limit());

        penampung.rewind();
        System.out.println("setelah rewind   : pos=" + penampung.position()
                + " lim=" + penampung.limit());
        System.out.println("baca ulang       : " + penampung.get());

        penampung.clear();
        System.out.println("setelah clear    : pos=" + penampung.position()
                + " lim=" + penampung.limit());
    }
}

Output yang Dihasilkan:

text
setelah allocate : pos=0 lim=8 cap=8
setelah 3 put    : pos=3 lim=8
setelah flip     : pos=0 lim=3
terbaca          : 10
terbaca          : 20
terbaca          : 30
setelah get semua: pos=3 lim=3
setelah rewind   : pos=0 lim=3
baca ulang       : 10
setelah clear    : pos=0 lim=8

Jejak penanda pada keluaran mengisahkan siklus yang utuh:

  1. allocate(8): buffer lahir kosong dengan pos=0 lim=8 cap=8 — mode tulis, karena limit menempel di capacity.
  2. Tiga kali put: setiap penulisan menaruh satu byte lalu menggeser position maju; setelah tiga nilai, pos=3 dan limit tetap 8.
  3. flip(): mode berpindah dari tulis ke baca dengan dua gerakan sekaligus — limit ditarik ke position lama (angka byte nyata: 3) dan position dipindah ke 0. Inilah momen buffer “mengunci” panjang datanya.
  4. hasRemaining() + get(): perulangan membaca selama position < limit; tiga nilai keluar berurutan dan pos berhenti tepat di lim=3.
  5. rewind(): mengembalikan position ke 0 tanpa menyentuh limit sehingga data yang sama bisa dibaca ulang — terbukti get() kembali menghasilkan 10.
  6. clear(): mengembalikan limit ke capacity dan position ke 0 — buffer siap diisi ulang. Perhatikan bahwa clear() tidak menghapus byte lama; byte 10 20 30 masih tersimpan, hanya saja akan tertimpa seiring penulisan baru.

Tipe Data Majemuk dan Pembungkus Array #

ByteBuffer tidak terbatas pada byte tunggal. Method putInt, putLong, putDouble menulis nilai multi-byte dalam urutan big-endian secara default (byte tertinggi duluan — dapat diubah dengan order()), dan pasangannya getInt, getLong, getDouble membaca balik dengan asumsi urutan serta posisi yang sama. Dua factory lain melengkapi cara membuat buffer: wrap(byte[]) membungkus array yang sudah ada tanpa menyalin, dan allocateDirect() meminta memori di luar heap Java.

java TipeBuffer.java
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class TipeBuffer {
    public static void main(String[] args) {
        ByteBuffer data = ByteBuffer.allocate(16);
        data.putInt(1_000_000);
        data.putLong(99L);
        data.put((byte) 'A');
        System.out.println("posisi setelah tulis : " + data.position());

        data.flip();
        System.out.println("int  : " + data.getInt());
        System.out.println("long : " + data.getLong());
        System.out.println("byte : " + (char) data.get());

        byte[] salinan = "Budi".getBytes(StandardCharsets.UTF_8);
        ByteBuffer bungkus = ByteBuffer.wrap(salinan);
        System.out.println("wrap  : pos=" + bungkus.position()
                + " lim=" + bungkus.limit()
                + " cap=" + bungkus.capacity());

        byte[] hasil = new byte[bungkus.remaining()];
        bungkus.get(hasil);
        System.out.println("pulih : " + new String(hasil, StandardCharsets.UTF_8));

        System.out.println("heap    : " + ByteBuffer.allocate(8).isDirect());
        System.out.println("direct  : " + ByteBuffer.allocateDirect(8).isDirect());
    }
}

Output yang Dihasilkan:

text
posisi setelah tulis : 13
int  : 1000000
long : 99
byte : A
wrap  : pos=0 lim=4 cap=4
pulih : Budi
heap    : false
direct  : true

Beberapa perilaku menonjol dari program tersebut:

  1. putInt + putLong + put: position melompat 4, lalu 8, lalu 1 — total 13 byte, sesuai ukuran biner tiap tipe. Pembacaan dengan getInt/getLong/get dalam urutan yang sama memulihkan nilai-nilai aslinya; urutan penulisan dan pembacaan harus cermin persis.
  2. wrap(salinan): buffer pembungkus langsung siap dibaca — limit diatur ke panjang array (4) — karena wrap menganggap array sudah terisi data. Buffer ini berbagi memori dengan array aslinya: mengubah array mengubah isi buffer.
  3. remaining(): selisih limit - position, dipakai untuk mengalokasikan array tujuan yang pas sebelum get(hasil) menyalin seluruh isi.
  4. isDirect(): buffer biasa tinggal di heap Java (nilai false) sehingga ia ikut digerakkan garbage collector; buffer direct (nilai true) dialokasikan di memori natif sistem operasi. Channel dapat memindahkan direct buffer ke disk tanpa salinan tambahan melalui heap — tepat untuk I/O besar dan berulang, dengan biaya alokasi yang lebih mahal.

Buffer sudah kita kuasai sebagai wadah; waktunya menghubungkan wadah itu ke berkas. Chapter berikutnya memperkenalkan FileChannel — saluran yang membaca dan menulis isi ByteBuffer langsung ke disk, lengkap dengan trik transferTo yang memindahkan data tanpa menyentuh ruang pengguna Java.

Referensi #

Berkas Sumber

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

AnatomiBuffer.java

Unduh berkas
java
import java.nio.ByteBuffer;

public class AnatomiBuffer {
    public static void main(String[] args) {
        ByteBuffer penampung = ByteBuffer.allocate(8);
        System.out.println("setelah allocate : pos=" + penampung.position()
                + " lim=" + penampung.limit()
                + " cap=" + penampung.capacity());

        penampung.put((byte) 10);
        penampung.put((byte) 20);
        penampung.put((byte) 30);
        System.out.println("setelah 3 put    : pos=" + penampung.position()
                + " lim=" + penampung.limit());

        penampung.flip();
        System.out.println("setelah flip     : pos=" + penampung.position()
                + " lim=" + penampung.limit());

        while (penampung.hasRemaining()) {
            System.out.println("terbaca          : " + penampung.get());
        }
        System.out.println("setelah get semua: pos=" + penampung.position()
                + " lim=" + penampung.limit());

        penampung.rewind();
        System.out.println("setelah rewind   : pos=" + penampung.position()
                + " lim=" + penampung.limit());
        System.out.println("baca ulang       : " + penampung.get());

        penampung.clear();
        System.out.println("setelah clear    : pos=" + penampung.position()
                + " lim=" + penampung.limit());
    }
}

TipeBuffer.java

Unduh berkas
java
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class TipeBuffer {
    public static void main(String[] args) {
        ByteBuffer data = ByteBuffer.allocate(16);
        data.putInt(1_000_000);
        data.putLong(99L);
        data.put((byte) 'A');
        System.out.println("posisi setelah tulis : " + data.position());

        data.flip();
        System.out.println("int  : " + data.getInt());
        System.out.println("long : " + data.getLong());
        System.out.println("byte : " + (char) data.get());

        byte[] salinan = "Budi".getBytes(StandardCharsets.UTF_8);
        ByteBuffer bungkus = ByteBuffer.wrap(salinan);
        System.out.println("wrap  : pos=" + bungkus.position()
                + " lim=" + bungkus.limit()
                + " cap=" + bungkus.capacity());

        byte[] hasil = new byte[bungkus.remaining()];
        bungkus.get(hasil);
        System.out.println("pulih : " + new String(hasil, StandardCharsets.UTF_8));

        System.out.println("heap    : " + ByteBuffer.allocate(8).isDirect());
        System.out.println("direct  : " + ByteBuffer.allocateDirect(8).isDirect());
    }
}