Program yang serius hampir selalu berurusan dengan berkas teks, misalnya berkas konfigurasi, catatan log, atau data yang diekspor dari aplikasi lain. Topik ini mempraktikkan dua cara membaca dan menulis berkas teks di Java, yaitu keluarga Reader dan Writer yang bekerja baris demi baris serta metode ringkas di kelas Files. Dasar encoding juga diurai, karena perbedaan penafsiran byte inilah yang membuat teks tampil rusak padahal berkasnya tidak terlihat bermasalah.
Menulis dan membaca baris demi baris#
BufferedWriter membungkus FileWriter lalu menambahkan penyangga (buffer) beserta metode newLine() untuk menulis pemisah baris, sedangkan BufferedReader membungkus FileReader dan menambahkan metode readLine(). Penyangga mengumpulkan beberapa tulisan lalu menuangkannya sekali jalan ke berkas, alih-alih menyentuh disk pada setiap panggilan tulis. Pemisah baris yang ditulis newLine() mengikuti konvensi sistem operasi tempat program berjalan, sehingga kodenya tetap benar di Linux maupun Windows. Charset diberikan secara eksplisit pada konstruktor keduanya, dan sejak Java 11 keduanya memang menerima charset. Program berikut menulis dua baris catatan, lalu membacanya kembali.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DemoBufferedReader {
public static void main(String[] args) throws IOException {
try (BufferedWriter keluar = new BufferedWriter(
new FileWriter("jurnal.txt", StandardCharsets.UTF_8))) {
keluar.write("Senin: menyelesaikan modul I/O");
keluar.newLine();
keluar.write("Selasa: membaca berkas teks");
keluar.newLine();
}
try (BufferedReader masuk = new BufferedReader(
new FileReader("jurnal.txt", StandardCharsets.UTF_8))) {
String baris = masuk.readLine();
while (baris != null) {
System.out.println(">> " + baris);
baris = masuk.readLine();
}
}
}
}>> Senin: menyelesaikan modul I/O
>> Selasa: membaca berkas teksMetode readLine() mengembalikan isi satu baris tanpa karakter pemisahnya, dan null ketika berkas habis. Idiom perulangan di atas menugaskan pembacaan di dalam kondisi while, sehingga baris terakhir tetap diproses dan perulangan berhenti tepat ketika null kembali. Menjalankan programnya dari terminal cukup dengan satu perintah berikut.
# berada di folder tempat DemoBufferedReader.java disimpan
java DemoBufferedReader.java>> Senin: menyelesaikan modul I/O
>> Selasa: membaca berkas teksJalan pintas dengan kelas Files#
Kelas Files menyediakan metode sekali panggil untuk kebutuhan teks yang umum. Files.write menyimpan seluruh daftar baris, Files.readAllLines membaca berkas menjadi List<String>, sedangkan Files.writeString dan Files.readString bekerja pada satu String utuh. Karena berkas dibaca dan ditulis utuh sekaligus, keluarga metode ini paling pas untuk berkas kecil hingga menengah yang pasti muat di memori. Program berikut mengulang pekerjaan sebelumnya dengan empat panggilan saja.
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class DemoFilesTeks {
public static void main(String[] args) throws IOException {
Path berkas = Path.of("jurnal.txt");
List<String> catatan = List.of(
"Senin: menyelesaikan modul I/O",
"Selasa: membaca berkas teks");
Files.write(berkas, catatan, StandardCharsets.UTF_8);
System.out.println("readAllLines:");
for (String baris : Files.readAllLines(berkas, StandardCharsets.UTF_8)) {
System.out.println(">> " + baris);
}
System.out.println("readString:");
System.out.println("---");
System.out.print(Files.readString(berkas, StandardCharsets.UTF_8));
System.out.println("---");
}
}readAllLines:
>> Senin: menyelesaikan modul I/O
>> Selasa: membaca berkas teks
readString:
---
Senin: menyelesaikan modul I/O
Selasa: membaca berkas teks
---Files.write menambahkan pemisah baris setelah setiap elemen daftar, sehingga teks yang terbaca lewat readString berakhir dengan pemisah baris juga. readAllLines membuang pemisah itu dari setiap elemen, sehingga daftarnya bersih untuk diolah lebih lanjut. Pilihan di antara keduanya sebaiknya mengikuti pola pemakaian, yaitu daftar siap pakai ketika isinya perlu baris demi baris, atau satu String ketika isinya diperlakukan sebagai kesatuan.
Encoding: kesepakatan karakter dan byte#
Encoding adalah kesepakatan yang menerjemahkan karakter menjadi byte, dan penerjemahannya berjalan dua arah melalui pengode (encoder) serta pendekode (decoder). Pada UTF-8, karakter ASCII memakai satu byte, sedangkan karakter lain memakai dua byte atau lebih. Java menyimpan char dan String secara internal dengan UTF-16, sehingga setiap kali teks bertemu berkas, penerjemahan itu pasti terjadi. Program berikut menulis teks berisi simbol derajat sebagai UTF-8, lalu membacanya dua kali dengan charset yang berbeda.
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class DemoCharsetBeda {
public static void main(String[] args) throws IOException {
Path berkas = Path.of("suhu.txt");
Files.writeString(berkas, "Suhu ruangan 24°C", StandardCharsets.UTF_8);
String salah = Files.readString(berkas, StandardCharsets.ISO_8859_1);
System.out.println("Dibaca ISO-8859-1 : " + salah);
System.out.println("Dibaca UTF-8 : " + Files.readString(berkas, StandardCharsets.UTF_8));
}
}Dibaca ISO-8859-1 : Suhu ruangan 24°C
Dibaca UTF-8 : Suhu ruangan 24°CSimbol derajat tersimpan sebagai dua byte, yaitu C2 dan B0. Pendekode UTF-8 menggabungkan keduanya menjadi satu karakter °, sedangkan pendekode ISO-8859-1 menerjemahkan tiap byte sebagai satu karakter terpisah, sehingga muncul pasangan ° yang tidak pernah ditulis. Kelas StandardCharsets menyimpan objek charset yang pasti ada di setiap JVM, sehingga kelas ini menjadi sumber charset yang aman untuk diberikan ke metode apa pun. Alur penerjemahan beserta akibat pilihan pendekode yang salah digambarkan berikut ini.
Kesalahan umum#
- Menulis dengan satu charset dan membaca dengan charset lain. Hasilnya teks rusak tanpa exception, seperti pasangan
°di atas. Nyatakan charset yang sama pada kedua arah. - Mengandalkan charset bawaan untuk berkas yang dibagikan ke mesin lain. Bawaannya berbeda pada JDK lama dan dapat berbeda antar lingkungan, sehingga keputusan yang penting dinyatakan eksplisit.
- Memuat berkas raksasa dengan
readStringataureadAllLines. Seluruh isinya menduduki memori sekaligus. PakaiBufferedReaderyang memproses baris demi baris. - Mengira
readLine()menyertakan pemisah baris. Pemisahnya dibuang, sehingga penulisan ulang baris memerlukannewLine()atau pemisah tambahan sendiri.
Ringkasan#
BufferedReader.readLine()membaca satu baris tanpa pemisahnya dan mengembalikannullsaat berkas habis, sedangkanBufferedWriter.newLine()menulis pemisah baris sesuai sistem operasi.Files.readAllLines,Files.readString,Files.write, danFiles.writeStringmenyelesaikan kebutuhan umum dalam satu panggilan, dengan syarat berkas muat di memori.- Encoding menerjemahkan karakter menjadi byte, dan pembacaan dengan charset yang berbeda menghasilkan teks rusak tanpa peringatan apa pun.
- UTF-8 adalah charset bawaan JVM sejak Java 18 dan menjadi pilihan aman untuk data lintas mesin.
Referensi#
- Reading, Writing, and Creating Files — Oracle — tutorial NIO.2 untuk membaca dan menulis berkas teks.
- BufferedReader (Java SE 21) — Oracle
— dokumentasi metode
readLine()dan perilakunya. - Charset (Java SE 21) — Oracle — dokumentasi kelas Charset sebagai representasi kesepakatan encoding.