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.
#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;
}07:00 22.5
08:00 23.1
09:00 24.8
rata-rata 23.5, maksimum 24.8Konversi 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.
#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;
}jam 7, suhu 22.5
jam 8, suhu 23.1
jam 9, suhu 24.8Ukuran 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.
// 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#
| Aspek | Berkas teks | Berkas biner |
|---|---|---|
| Keterbacaan | dapat dibuka dan diperiksa dengan editor teks | berupa byte mentah yang sulit dibaca langsung |
| Konversi | teks diterjemahkan menjadi angka saat diurai | tanpa konversi, nilai dibaca apa adanya |
| Ukuran | lebih besar untuk bilangan panjang | lebih padat karena menyalin representasi memori |
| Ketahanan format | tahan terhadap perubahan tata letak kolom | rusak apabila susunan struct berubah |
| Contoh pemakaian | log, konfigurasi, data untuk manusia | arsip 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 denganwrite()sertaread(). - Menganggap
sizeof(struct)sama dengan jumlah ukuran anggotanya. Padding menambah ukuran rekaman, sehingga perhitungan byte memakaisizeofsecara langsung, bukan penjumlahan manual. - Memakai
std::getlineuntuk berkas biner. Pembacaan mengikuti karakter baris baru, bukan batas rekaman. Perbaikannya membaca denganread()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()). Keadaaneof()baru aktif setelah pembacaan melewati akhir berkas, sehingga perulangan itu mencoba membaca satu rekaman ekstra dan memproses byte sampahnya. Perbaikannya menjadikan hasilread()sebagai kondisi perulangan, sebagaimana program di atas.
Ringkasan#
- Berkas teks diurai baris demi baris dengan
std::getline, lalu setiap baris dipecah menjadi kolom olehstd::istringstream. - Berkas biner menyimpan struct berukuran tetap melalui
write()dan membacanya melaluiread()dengan modestd::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#
- std::basic_ostream::write — cppreference — Dokumentasi penulisan deret byte ke stream.
- std::basic_istream::read — cppreference — Dokumentasi pembacaan deret byte dari stream.
- std::ios_base::openmode — cppreference
— Konstanta mode pembukaan berkas, termasuk
std::ios::binary. - Basic file I/O — LearnCpp — Dasar membaca dan menulis berkas dengan file stream.
- Random file IO — LearnCpp — Pembacaan dan penulisan rekaman berukuran tetap pada posisi tertentu.
- std::basic_istream::seekg — cppreference — Dokumentasi pemindahan posisi baca ke offset tertentu.