Data yang diterima dari jaringan maupun yang ditulis ke berkas selalu bergerak sebagai deretan nilai yang mengalir, dan Java menyebut mekanisme itu stream. Topik ini membedakan dua keluarga besar stream pada paket java.io, yaitu stream byte untuk data biner apa pun dan stream karakter untuk teks. Setelah menelusuri contoh di sini, empat kelas akar berupa InputStream, OutputStream, Reader, dan Writer tidak lagi asing, demikian pula alasan Java menyediakan dua dunia yang terpisah.
Dua jenis stream: byte dan karakter#
Stream byte memindahkan data sebagai deretan byte mentah, sedangkan stream karakter memindahkan data sebagai deretan karakter yang sudah melalui encoding, yakni kesepakatan penerjemahan antara karakter dan byte. Kelas abstrak InputStream dan OutputStream menjadi akar keluarga byte, sedangkan Reader dan Writer menjadi akar keluarga karakter. Untuk gambar, suara, dan data biner lainnya, keluarga byte adalah pilihan yang tepat. Untuk teks, keluarga karakter menyelamatkan kode dari pekerjaan mengode dan mendekode secara manual.
Perbedaan keduanya paling mudah dilihat pada berkas yang memuat satu karakter non-ASCII. Program berikut menulis teks Suhu 24°C sebagai byte UTF-8, lalu membacanya kembali byte demi byte. Simbol derajat itu tersimpan sebagai dua byte, dan keduanya tampil sebagai angka 194 serta 176, bukan sebagai satu karakter °.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DemoBacaByte {
public static void main(String[] args) throws IOException {
try (FileOutputStream keluar = new FileOutputStream("sensor.txt")) {
keluar.write("Suhu 24°C".getBytes(StandardCharsets.UTF_8));
}
int jumlah = 0;
try (FileInputStream masuk = new FileInputStream("sensor.txt")) {
int b = masuk.read();
while (b != -1) {
System.out.println(b + " -> '" + (char) b + "'");
jumlah++;
b = masuk.read();
}
}
System.out.println("Total byte dibaca: " + jumlah);
}
}83 -> 'S'
117 -> 'u'
104 -> 'h'
117 -> 'u'
32 -> ' '
50 -> '2'
52 -> '4'
194 -> 'Â'
176 -> '°'
67 -> 'C'
Total byte dibaca: 10Keluarannya memperlihatkan sepuluh byte untuk sembilan karakter. Metode read() pada InputStream mengembalikan nilai int berupa satu byte, dengan rentang 0 sampai 255, dan mengembalikan -1 ketika data habis, sehingga perulangan while di atas berhenti dengan rapi. Cast (char) hanya menerjemahkan angka menjadi karakter sesuai nomornya, tanpa pengetahuan tentang UTF-8, sehingga pasangan byte 194 dan 176 tampil sebagai dua karakter yang terlihat asing. Menjalankan programnya dari terminal cukup dengan satu perintah berikut.
# berada di folder tempat DemoBacaByte.java disimpan
java DemoBacaByte.java83 -> 'S'
117 -> 'u'
104 -> 'h'
117 -> 'u'
32 -> ' '
50 -> '2'
52 -> '4'
194 -> 'Â'
176 -> '°'
67 -> 'C'
Total byte dibaca: 10Keluarga Reader dan Writer#
Keluarga Reader dan Writer menangani teks sebagai aliran karakter. FileReader membaca berkas teks dengan mendekode byte di dalamnya menjadi karakter sesuai charset yang diberikan, yakni kesepakatan pengodean yang dipilih, sedangkan FileWriter melakukan kebalikannya saat menulis. Sejak Java 11, keduanya menerima charset secara eksplisit pada konstruktornya, sehingga hasil pembacaan tidak bergantung pada pengaturan bawaan mesin. Program berikut menulis teks yang sama dengan program sebelumnya, tetapi kali ini lewat FileWriter, lalu membacanya kembali sebagai karakter.
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DemoBacaKarakter {
public static void main(String[] args) throws IOException {
try (FileWriter keluar = new FileWriter("sensor.txt", StandardCharsets.UTF_8)) {
keluar.write("Suhu 24°C");
}
int jumlah = 0;
try (FileReader masuk = new FileReader("sensor.txt", StandardCharsets.UTF_8)) {
int c = masuk.read();
while (c != -1) {
System.out.println(c + " -> '" + (char) c + "'");
jumlah++;
c = masuk.read();
}
}
System.out.println("Total karakter dibaca: " + jumlah);
}
}83 -> 'S'
117 -> 'u'
104 -> 'h'
117 -> 'u'
32 -> ' '
50 -> '2'
52 -> '4'
176 -> '°'
67 -> 'C'
Total karakter dibaca: 9Sembilan karakter terbaca utuh, dan simbol derajat kini muncul sebagai satu karakter bernilai 176, bukan dua byte terpisah. Metode read() pada Reader mengembalikan kode karakternya dan tetap mengembalikan -1 ketika data habis, sehingga bentuk perulangannya mirip dengan versi byte. Tabel berikut merangkum padanan kelas pada kedua keluarga.
| Kebutuhan | Keluarga byte | Keluarga karakter |
|---|---|---|
| Kelas akar | InputStream dan OutputStream | Reader dan Writer |
| Contoh kelas untuk berkas | FileInputStream, FileOutputStream | FileReader, FileWriter |
| Data yang cocok | gambar, suara, byte mentah | teks |
Demo berikut menghitung ulang jumlah karakter dan jumlah byte UTF-8 dari teks apa pun yang diketik. Ubah isinya, tambahkan huruf non-ASCII, lalu amati selisih kedua hitungan.
Pada teks bawaan Suhu 24°C, hitungan karakternya sembilan sedangkan hitungan byte-nya sepuluh. Menambahkan satu emoji membuat hitungan karakter naik satu, tetapi hitungan byte naik empat, karena karakter di luar blok dasar Unicode memakai byte lebih banyak lagi. Selisih kedua hitungan inilah alasan Java memisahkan dua keluarga stream sejak awal.
Jembatan dari byte ke karakter#
Sebagian besar sumber data menyampaikan byte, baik berkas, koneksi jaringan, maupun aliran masukan sistem. Kelas InputStreamReader menjadi jembatan yang membungkus InputStream lalu mendekode byte-nya menjadi karakter, sedangkan OutputStreamWriter menangani arah sebaliknya. Charset pada jembatan ini dinyatakan secara eksplisit, sehingga hasil dekodenya pasti. Program berikut menulis byte seperti contoh pertama, tetapi membacanya lewat jembatan.
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class DemoJembatanKarakter {
public static void main(String[] args) throws IOException {
try (FileOutputStream keluar = new FileOutputStream("sensor.txt")) {
keluar.write("Suhu 24°C".getBytes(StandardCharsets.UTF_8));
}
try (FileInputStream masuk = new FileInputStream("sensor.txt");
BufferedReader pembaca = new BufferedReader(
new InputStreamReader(masuk, StandardCharsets.UTF_8))) {
String isi = pembaca.readLine();
System.out.println("Isi : " + isi);
System.out.println("Panjang string : " + isi.length());
}
}
}Isi : Suhu 24°C
Panjang string : 9Byte yang sama kini terbaca sebagai satu teks utuh, dan String.length() menegaskan bahwa isinya sembilan karakter. BufferedReader yang membungkus jembatan menambahkan metode readLine(), sehingga pembacaan per baris menjadi ringkas. Rantai pembungkusan semacam ini adalah pola kerja yang lazim di java.io, yaitu stream inti yang menyentuh sumber data dan stream pembungkus yang menambahkan kenyamanan. Rangkaian kelas pada kedua keluarga beserta posisi jembatannya digambarkan berikut ini.
Kesalahan umum#
- Memakai stream byte untuk teks. Byte terbaca per unit dan karakter multi-byte tampil rusak seperti pasangan 194 dan 176 pada contoh pertama. Pakai
ReaderdanWriteruntuk teks. - Mengabaikan nilai -1 dari
read(). Setelah data habis,read()terus mengembalikan -1, sehingga perulangan baca yang tidak mengujinya tidak pernah berhenti. Uji -1 sebagai tanda data habis. - Membaca atau menulis tanpa menutup stream. Data yang masih berada di penyangga (buffer) belum tentu sempat ditulis, dan berkas yang terbuka menahan sumber daya sistem. Bungkus stream dengan try-with-resources.
- Menganggap satu karakter selalu satu byte. Pada UTF-8, karakter non-ASCII memakai dua byte atau lebih, sehingga jumlah byte bisa lebih besar daripada jumlah karakternya.
Ringkasan#
- Stream byte dengan akar
InputStreamdanOutputStreammemindahkan data mentah per byte, sedangkan stream karakter dengan akarReaderdanWritermemindahkan teks yang sudah didekode. - Metode
read()mengembalikanintdan bernilai -1 saat data habis, sehingga perulangan baca mengujinya sebagai tanda berhenti. InputStreamReaderdanOutputStreamWritermenjadi jembatan dari byte ke karakter, dengan charset yang dapat dinyatakan eksplisit.- Jumlah byte sebuah teks tidak selalu sama dengan jumlah karakternya pada UTF-8.
Referensi#
- Byte Streams — Oracle
— tutorial resmi stream byte beserta contoh
FileInputStreamdanFileOutputStream. - Character Streams — Oracle
— tutorial resmi stream karakter beserta contoh
FileReaderdanFileWriter. - Reader (Java SE 21) — Oracle
— dokumentasi kelas Reader beserta kontrak metode
read().