I/O teks versus biner

Parsing berbasis baris dan read/write untuk rekaman biner.

Daftar isi

Berkas teks dan berkas biner menyimpan data yang sama dengan dua representasi berbeda. Topik ini membahas penguraian (parsing) berkas teks baris demi baris dengan std::getline dan std::istringstream, penulisan serta pembacaan rekaman biner berukuran tetap dengan write() dan read(), dan penimbangan kapan masing-masing representasi lebih tepat. Kedua program contoh membuat berkasnya sendiri, sehingga dapat dicoba tanpa berkas pendamping.

Parsing berbasis baris pada berkas teks#

Berkas teks menyimpan data sebagai deret karakter yang dapat dibaca manusia, umumnya satu catatan per baris. Pola penguraiannya terdiri atas dua lapis, yaitu std::getline mengambil satu baris utuh dari std::ifstream, kemudian std::istringstream memecah baris itu menjadi kolom-kolomnya. Pembagian per baris membuat kesalahan pada satu catatan tidak menjalar ke catatan lainnya.

cpp parsing-teks.cpp
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

int main() {
    // buat berkas teks berisi pengukuran suhu
    std::ofstream keluar("pengukuran.txt");
    keluar << "07:00 22.5\n";
    keluar << "08:00 23.1\n";
    keluar << "09:00 24.8\n";
    keluar.close();

    // urai setiap baris menjadi waktu dan nilai suhu
    std::ifstream masuk("pengukuran.txt");
    std::string baris;
    double total = 0.0;
    double maksimum = 0.0;
    int jumlah = 0;
    while (std::getline(masuk, baris)) {
        std::istringstream bagian(baris);
        std::string waktu;
        double suhu = 0.0;
        if (bagian >> waktu >> suhu) {
            total += suhu;
            ++jumlah;
            if (suhu > maksimum) {
                maksimum = suhu;
            }
            std::cout << waktu << " " << suhu << "\n";
        } else {
            std::cout << "baris tidak valid: " << baris << "\n";
        }
    }
    std::cout << std::fixed << std::setprecision(1)
              << "rata-rata " << total / jumlah
              << ", maksimum " << maksimum << "\n";
    return 0;
}
text Keluaran
07:00 22.5
08:00 23.1
09:00 24.8
rata-rata 23.5, maksimum 24.8

Konversi teks menjadi angka dilakukan langsung oleh operator >> pada std::istringstream, yang mengikuti tipe variabel sasarannya. Percabangan if (bagian >> waktu >> suhu) sekaligus berperan sebagai validasi, karena baris yang tidak sesuai format dilaporkan dan dilewati, sedangkan baris yang sah masuk ke perhitungan statistik. Rata-rata dan nilai maksimum pada keluaran dihitung dari baris yang berhasil diurai saja.

Perlu dicatat bahwa mode teks pada sebagian platform menerjemahkan akhir baris. Windows menyimpan akhir baris sebagai pasangan carriage return dan line feed, sedangkan Linux tidak menerjemahkan apa pun. Berkas yang ditulis di Windows karenanya masih membawa karakter \r di ujung tiap barisnya apabila dibaca kembali di Linux. Karakter sisa itu membuat kolom terakhir terlihat berbeda padahal nilai angkanya sama, sehingga pada berkas lintas platform, \r perlu dibuang sebelum baris diurai.

Rekaman biner dengan write dan read#

Berkas biner menyimpan nilai dalam bentuk byte mentah sebagaimana tersimpan di memori. Satu rekaman (record) biasanya berupa struct berukuran tetap, sehingga seluruh anggotanya ditulis sekaligus dengan write() dan dibaca kembali dengan read(). Kedua metode tersebut menerima alamat data yang di-cast menjadi char* karena keduanya bekerja atas deret byte, bukan atas tipe tertentu.

cpp rekaman-biner.cpp
#include <cstddef>
#include <fstream>
#include <iostream>

struct Pengukuran {
    int jam;
    double suhu;
};

int main() {
    const Pengukuran data[] = {
        {7, 22.5},
        {8, 23.1},
        {9, 24.8},
    };
    const std::size_t banyak = sizeof(data) / sizeof(data[0]);

    // tulis rekaman biner apa adanya ke berkas
    std::ofstream keluar("pengukuran.bin", std::ios::binary);
    for (std::size_t i = 0; i < banyak; ++i) {
        keluar.write(reinterpret_cast<const char*>(&data[i]), sizeof(Pengukuran));
    }
    keluar.close();

    // baca kembali rekaman satu per satu
    std::ifstream masuk("pengukuran.bin", std::ios::binary);
    Pengukuran bacaan{};
    while (masuk.read(reinterpret_cast<char*>(&bacaan), sizeof(Pengukuran))) {
        std::cout << "jam " << bacaan.jam
                  << ", suhu " << bacaan.suhu << "\n";
    }
    return 0;
}
text Keluaran
jam 7, suhu 22.5
jam 8, suhu 23.1
jam 9, suhu 24.8

Ukuran Pengukuran pada g++ adalah 16 byte, bukan 12, karena compiler menyelipkan ruang kosong (padding) agar double menempati alamat yang sesuai aturan perataannya. Jumlah byte itu menjadi ukuran yang pasti bagi satu rekaman, dan berkas contoh menampung tiga rekaman. Perulangan pembacaan berakhir ketika sisa byte kurang dari ukuran satu rekaman, karena read() mengaktifkan failbit dan membuat stream bernilai false pada kondisi while.

Ukuran rekaman yang tetap membuka kemampuan lain, yaitu akses acak (random access). Karena setiap rekaman menempati sizeof(Pengukuran) byte, rekaman berindeks i berada tepat pada offset i * sizeof(Pengukuran) dari awal berkas. Fungsi seekg() memindahkan posisi baca ke byte tertentu, sehingga satu rekaman di tengah arsip dapat dibaca tanpa membaca seluruh berkasnya terlebih dahulu.

cpp Pola akses acak: membaca rekaman kedua
// tidak-dijalankan (fragmen pola, bukan program utuh)
masuk.seekg(1 * sizeof(Pengukuran));  // lompat ke rekaman indeks 1
masuk.read(reinterpret_cast<char*>(&bacaan), sizeof(Pengukuran));

Indeks rekaman dimulai dari nol, sehingga nilai 1 pada contoh menunjuk rekaman kedua. Pada mode biner, argumen seekg() ditafsirkan sebagai jumlah byte yang dihitung dari awal berkas, kemudian read() membaca persis ukuran satu rekaman dari posisi tersebut. Kemampuan ini wajar dipakai pada arsip yang besar, sedangkan berkas teks tidak menawarkan padanannya karena panjang barisnya berbeda-beda.

Memilih representasi teks atau biner#

AspekBerkas teksBerkas biner
Keterbacaandapat dibuka dan diperiksa dengan editor teksberupa byte mentah yang sulit dibaca langsung
Konversiteks diterjemahkan menjadi angka saat diuraitanpa konversi, nilai dibaca apa adanya
Ukuranlebih besar untuk bilangan panjanglebih padat karena menyalin representasi memori
Ketahanan formattahan terhadap perubahan tata letak kolomrusak apabila susunan struct berubah
Contoh pemakaianlog, konfigurasi, data untuk manusiaarsip rekaman berukuran tetap, data lokal berukuran besar

Aturan praktisnya, teks dipilih apabila data perlu diperiksa atau disunting manusia, sedangkan biner dipilih apabila kepadatan dan kecepatan lebih penting daripada keterbacaan. Keduanya juga dapat dipakai bersamaan dalam satu aplikasi, misalnya log teks untuk operasi harian dan arsip biner untuk data historis. Apabila pertukaran data antarplatform diperlukan, format teks yang terstruktur biasanya lebih aman daripada rekaman biner mentah.

Klaim kepadatan biner juga sebaiknya diperiksa terhadap data yang nyata. Suhu 23.1 sepanjang empat karakter teks sebenarnya lebih ringkas daripada delapan byte double, sehingga keunggulan ukuran tidak selalu tercapai per nilai. Arsip biner tetap menghemat karena bebas dari pemisah baris, baris judul, dan pengulangan nama kolom, selain tidak menanggung biaya konversi teks ke angka setiap kali dibaca. Kecepatan pembacaan tanpa penguraian inilah alasan utama arsip berukuran besar disimpan sebagai rekaman biner.

Kesalahan umum#

Kekeliruan berikut sering muncul saat berkas biner pertama kali ditangani.

  • Lupa menyertakan mode std::ios::binary. Pada sebagian platform, mode teks mengubah karakter baris baru sehingga byte data dapat ikut terubah. Perbaikannya selalu menambahkan mode tersebut untuk berkas yang dibaca dan ditulis dengan write() serta read().
  • Menganggap sizeof(struct) sama dengan jumlah ukuran anggotanya. Padding menambah ukuran rekaman, sehingga perhitungan byte memakai sizeof secara langsung, bukan penjumlahan manual.
  • Memakai std::getline untuk berkas biner. Pembacaan mengikuti karakter baris baru, bukan batas rekaman. Perbaikannya membaca dengan read() sepanjang ukuran satu rekaman.
  • Menulis struct yang berisi pointer atau std::string. Yang tersalin hanyalah byte penunjuknya, bukan data yang ditunjuk, sehingga berkas hasilnya tidak berguna. Perbaikannya menyimpan hanya tipe berukuran tetap seperti angka dan array karakter.
  • Mengulang pembacaan dengan while (!masuk.eof()). Keadaan eof() baru aktif setelah pembacaan melewati akhir berkas, sehingga perulangan itu mencoba membaca satu rekaman ekstra dan memproses byte sampahnya. Perbaikannya menjadikan hasil read() sebagai kondisi perulangan, sebagaimana program di atas.

Ringkasan#

  • Berkas teks diurai baris demi baris dengan std::getline, lalu setiap baris dipecah menjadi kolom oleh std::istringstream.
  • Berkas biner menyimpan struct berukuran tetap melalui write() dan membacanya melalui read() dengan mode std::ios::binary.
  • Ukuran rekaman ditentukan oleh sizeof, termasuk padding yang disisipkan compiler.
  • Teks unggul dalam keterbacaan dan ketahanan format, sedangkan biner unggul dalam kepadatan, tetapi tidak portabel antarplatform.

Referensi#