File teks itu ramah manusia tetapi boros: angka 4096.0 menjadi tujuh karakter di disk, sementara komputernya hanya mengenal delapan byte. File biner membalik kesepakatan ini — data ditulis persis sebagaimana disimpan di memori, byte demi byte, sehingga hemat ruang, cepat, dan tidak perlu proses konversi. Harganya: file tidak lagi bisa dibaca manusia, dan formatnya terikat pada representasi mesin yang menulisnya. Chapter ini memperkenalkan mode ios::binary, pasangan fungsi write/read dengan reinterpret_cast, lalu — bagian yang tidak boleh dilewat — risiko endianness, struct padding, dan portabilitas yang membuat file biner jauh lebih licin daripada teks.

Teks versus Biner: Dua Format, Dua Kontrak #

Sebelum menulis kode, mari samakan pemahaman tentang apa yang sebenarnya berbeda:

AspekFile teksFile biner
IsiKarakter yang dibaca manusiaByte mentah persis memori
4096.0 disimpan sebagai4096 (4-5 byte karakter)8 byte IEEE 754
Konversi saat tulis/bacaAda (formatting + parsing)Tidak ada
Ukuran untuk data numerik besarBorosKompak
Bisa dibuka di text editorYaTidak bermakna
Portabilitas antar mesinBaikBergantung format (lihat risiko)

Satu hal yang sering disalahpahami: ios::binary tidak mengubah data menjadi “terenkripsi” atau menambah apa pun. Mode ini hanya melarang stream melakukan terjemahan karakter — pada Windows, misalnya, '\n' dalam mode teks diterjemahkan menjadi dua byte '\r\n' saat ditulis dan dibalik saat dibaca, yang pada file biner justru merusak data karena byte 0x0A bisa jadi bagian dari angka, bukan baris baru. Di Linux terjemahan itu tidak terjadi, tetapi tulis std::ios::binary tetap: kebiasaan ini membuat kode Anda benar di semua platform.

write dan read: Mengangkut Byte Mentah #

Pasangan fungsinya berdiri berhadapan: ostream::write(alamat, jumlah) menyalin jumlah byte dari alamat memori ke stream, dan istream::read(alamat, jumlah) menyalin sebaliknya. Karena fungsi ini menuntut alamat byte, kita meminjam jasa reinterpret_cast<char*> — cast yang secara sadar “melabeli ulang” double* sebagai char* tanpa mengubah datanya:

cpp
#include <fstream>
#include <iostream>

int main() {
    const int jumlahSampel = 4;
    double sampel[jumlahSampel] = {1.5, -2.25, 300.125, 4096.0};

    // 1. Tulis array ke file biner
    std::ofstream keluar("sampel.bin", std::ios::binary);
    if (!keluar) {
        std::cerr << "Gagal membuka sampel.bin untuk ditulis\n";
        return 1;
    }
    keluar.write(reinterpret_cast<char*>(sampel), sizeof(sampel));
    keluar.close();                     // pastikan ter-flush sebelum dibaca

    // 2. Periksa ukuran file: 4 x 8 byte per double
    std::ifstream masuk("sampel.bin", std::ios::binary);
    if (!masuk) {
        std::cerr << "Gagal membuka sampel.bin untuk dibaca\n";
        return 1;
    }
    masuk.seekg(0, std::ios::end);
    std::streampos ukuran = masuk.tellg();
    std::cout << "Ukuran sampel.bin: " << ukuran << " byte\n";
    masuk.seekg(0, std::ios::beg);

    // 3. Baca kembali byte yang sama, susun menjadi array
    double hasil[jumlahSampel] = {};
    masuk.read(reinterpret_cast<char*>(hasil), sizeof(hasil));
    std::streamsize terbaca = masuk.gcount();
    std::streamsize diharapkan = static_cast<std::streamsize>(sizeof(hasil));
    if (terbaca != diharapkan) {
        std::cerr << "Data tidak lengkap: " << terbaca << " dari "
                  << diharapkan << " byte\n";
        return 1;
    }
    for (int i = 0; i < jumlahSampel; ++i) {
        std::cout << "sampel[" << i << "] = " << hasil[i] << '\n';
    }
    return 0;
}

Output yang Dihasilkan:

text
Ukuran sampel.bin: 32 byte
sampel[0] = 1.5
sampel[1] = -2.25
sampel[2] = 300.125
sampel[3] = 4096
  1. Baris 11-16 (tulis): sizeof(sampel) adalah 4 × 8 = 32 byte — satu panggilan write mengangkut seluruh array sekaligus. close() eksplisit di sini fungsional: kita membuka file yang sama untuk dibaca, jadi buffer tulis harus selesai dulu.
  2. Baris 19-26 (ukuran file): seekg(0, std::ios::end) memindahkan posisi baca ke ujung file, dan tellg() melaporkan posisi itu dalam byte — pasangan klasik untuk mengukur ukuran file. seekg(0, std::ios::beg) lalu mengembalikan posisi ke awal sebelum pembacaan.
  3. Baris 30-31 (baca): byte yang tersalin jatuh tepat ke array hasil; tidak ada parsing, tidak ada konversi, hanya salinan memori.
  4. Baris 32-37 (verifikasi jumlah byte): gcount() melaporkan berapa byte yang benar-benar terbaca oleh read terakhir. Bila file dipangkas atau disk bermasalah, program berhenti dengan pesan — bukan melanjutkan dengan data setengah jadi.

Nilai yang tercetak identik dengan yang ditulis, dan ukuran file 32 byte membuktikan kehematan biner. Sebagai pembanding mental: versi teksnya (1.5 -2.25 300.125 4096) memakan 24 karakter plus pemisah — lebih kecil di kasus ini, tetapi coba bayangkan satu juta sampel dua digit presisinya; biner tetap 8 MB, teks bisa membengkak dua sampai tiga kali lipat.

Risiko reinterpret_cast: Endianness, Padding, Portabilitas #

Di sinilah bagian yang membuat file biner perlu diperlakukan dengan hormat. Tiga risiko utamanya:

  • Endianness: urutan byte sebuah int tidak sama di semua CPU. Mesin x86 dan ARM umumnya little-endian (byte terkecil dulu), sementara sebagian arsitektur lain big-endian. File yang ditulis di mesin little-endian akan terbaca sebagai angka acak oleh program big-endian yang membaca byte mentah tanpa konversi.
  • Struct padding: compiler menyelipkan byte kosong antar member struct agar aksesnya cepat (materi Chapter Member Variables). Jumlah padding bisa berbeda antar compiler dan antar pengaturan flag — menulis write(reinterpret_cast<char*>(&s), sizeof(s)) untuk satu struct berarti menggembok file Anda pada tata letak memori mesin tertentu.
  • Ukuran tipe: int dijamin minimal 16 bit oleh standar, bukan tepat 32; long berbeda lebar antara Windows dan Linux. File biner yang menempel pada sizeof(int) bisa rusak lintas platform.
cpp Lebih aman: tulis per field dengan tipe berlebar tetap
// butuh #include <cstdint>
std::int32_t stok = 12;
keluar.write(reinterpret_cast<char*>(&stok), sizeof(stok));  // selalu 4 byte
// untuk struct: tulis member satu per satu, bukan sizeof(struct)

Karena itu kontrak kerja yang sehat: tentukan format file secara eksplisit (tipe apa, berapa byte, urutan byte mana), tulis field per field dengan tipe berlebar tetap (std::int32_t, std::int64_t, double), dan dokumentasikan formatnya. File yang dibaca kembali oleh program yang sama, di mesin yang sama, pada hari yang berbeda — seperti contoh di atas — aman; file yang akan menyeberang platform perlu disiplin tambahan ini.

Kapan Biner Tepat, Kapan Teks #

Pertimbangan pemilihannya bisa diringkas dalam empat petunjuk. Gunakan biner bila volume data numerik besar (dataset sensor, citra, audio, hasil simulasi) dan kecepatan/ukuran lebih penting daripada keterbacaan; bila formatnya internal — hanya ditulis dan dibaca program Anda sendiri. Gunakan teks bila data perlu diperiksa manusia, dibaca alat lain (spreadsheet, grep, sistem kontrol versi), atau ditukar lintas platform dengan mudah — CSV yang kita kerjakan dua chapter terakhir adalah wujudnya. Kasus di antara: file hasil (game save, dokumen) lazim memakai format terdefinisi baik yang memadukan keduanya.

Dengan teks dan biner sama-sama terkendali, semua operasi kita masih berputar pada satu file tertentu. Chapter berikutnya memperluas medan kerja ke keseluruhan sistem berkas: membuat folder, menelusuri isi direktori, dan mengukur file lewat <filesystem>.

Referensi #