Membaca dan Menulis Teks

Membaca dan menulis berkas teks dengan BufferedReader dan Files sambil memahami dasar encoding.

Daftar isi

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.

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.

java DemoBufferedReader.java
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();
            }
        }
    }
}
text Keluaran
>> Senin: menyelesaikan modul I/O
>> Selasa: membaca berkas teks

Metode 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.

bash
# berada di folder tempat DemoBufferedReader.java disimpan
java DemoBufferedReader.java
text Keluaran
>> Senin: menyelesaikan modul I/O
>> Selasa: membaca berkas teks

Jalan 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.

java DemoFilesTeks.java
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("---");
    }
}
text Keluaran
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.

java DemoCharsetBeda.java
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));
    }
}
text Keluaran
Dibaca ISO-8859-1 : Suhu ruangan 24°C
Dibaca UTF-8      : Suhu ruangan 24°C

Simbol 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.

karakter '°'U+00B0 di String Javapengode UTF-8byte: C2 B0isi berkas di diskpendekode UTF-8karakter '°'kembali utuhpendekode ISO-8859-1karakter 'Â' + '°'dua karakter: teks rusak
Perjalanan satu karakter dari Java ke berkas dan kembali lagi

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 readString atau readAllLines. Seluruh isinya menduduki memori sekaligus. Pakai BufferedReader yang memproses baris demi baris.
  • Mengira readLine() menyertakan pemisah baris. Pemisahnya dibuang, sehingga penulisan ulang baris memerlukan newLine() atau pemisah tambahan sendiri.

Ringkasan#

  • BufferedReader.readLine() membaca satu baris tanpa pemisahnya dan mengembalikan null saat berkas habis, sedangkan BufferedWriter.newLine() menulis pemisah baris sesuai sistem operasi.
  • Files.readAllLines, Files.readString, Files.write, dan Files.writeString menyelesaikan 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#