Pembacaan dan penulisan sederhana bergerak lurus dari awal berkas menuju akhirnya, sedangkan akses acak (random access) memungkinkan program melompat ke posisi byte mana pun sebelum membaca atau menulis. Topik ini membahas penunjuk posisi yang dimiliki setiap stream, fungsi ftell untuk membaca posisi tersebut, fseek untuk berpindah, rewind untuk kembali ke awal, serta idiom menentukan ukuran berkas. Setelah membacanya, catatan ke-n dapat diambil tanpa membaca seluruh berkas terlebih dahulu.
Penunjuk posisi dan ftell#
Setiap stream yang terbuka memiliki penunjuk posisi (file position indicator), yaitu nomor byte berikutnya yang akan dibaca atau ditulis, dihitung dari awal berkas. Pada stream yang dibuka dengan mode "r", penunjuknya dimulai dari 0 dan bergerak maju setiap kali terjadi bacaan, misalnya maju 9 byte setelah satu baris 9 byte terbaca. Fungsi ftell mengembalikan posisi penunjuk tersebut bertipe long, atau nilai negatif -1L bila posisi tidak dapat ditentukan. Nilai yang dikembalikan ftell selalu relatif terhadap awal berkas, bukan terhadap baris, sehingga penunjuk posisi diperlakukan sebagai nomor byte murni. Angka inilah yang dicetak program pada bagian berikutnya untuk membuktikan hasil setiap lompatan.
// tidak-dijalankan
long posisi = ftell(berkas);Nilai posisi ini dipakai bersama oleh seluruh fungsi akses acak, karena argumen offset fseek dan hasil ftell menggunakan satuan yang sama. Satu hal perlu diingat sejak awal, yaitu posisi pada mode "a" tidak mengikat penulisan karena tulisan selalu menuju ujung berkas, berapa pun posisi bacanya. Penjelasan lengkap ketiga mode dengan tanda + sudah dibahas pada topik fopen, fclose, dan Mode Berkas.
Berpindah posisi dengan fseek#
Berkas sensor.txt berikut memuat lima catatan suhu, dan setiap catatannya berukuran sama, yaitu 8 karakter ditambah satu pindah baris sehingga 9 byte per baris:
Sen 27.5
Sel 28.1
Rab 26.9
Kam 27.8
Jum 29.3Dengan catatan berukuran tetap, posisi setiap catatan mudah dihitung, yaitu catatan ke-i berada pada offset 9 * i. Fungsi fseek memindahkan penunjuk posisi dan dipanggil dengan pola fseek(stream, offset, asal). Asalnya ada tiga pilihan, yaitu SEEK_SET dari awal berkas, SEEK_CUR dari posisi saat ini, dan SEEK_END dari ujung berkas. Nilai kembanya 0 saat berhasil dan -1 saat gagal:
#include <stdio.h>
int main(void) {
FILE *berkas = fopen("sensor.txt", "r");
if (berkas == NULL) {
perror("fopen sensor.txt");
return 1;
}
char baris[32];
/* lompat ke catatan ketiga, yaitu offset 9 x 2 */
fseek(berkas, 9L * 2, SEEK_SET);
printf("Posisi sekarang : %ld\n", ftell(berkas));
if (fgets(baris, sizeof baris, berkas) != NULL) {
printf("Catatan ketiga : %s", baris);
}
/* geser dua byte dari posisi sekarang */
fseek(berkas, 2L, SEEK_CUR);
printf("Posisi sekarang : %ld\n", ftell(berkas));
if (fgets(baris, sizeof baris, berkas) != NULL) {
printf("Sisa baris : %s", baris);
}
/* mundur satu catatan dari ujung berkas */
fseek(berkas, -9L, SEEK_END);
printf("Posisi sekarang : %ld\n", ftell(berkas));
if (fgets(baris, sizeof baris, berkas) != NULL) {
printf("Catatan terakhir: %s", baris);
}
fclose(berkas);
return 0;
}Posisi sekarang : 18
Catatan ketiga : Rab 26.9
Posisi sekarang : 29
Sisa baris : m 27.8
Posisi sekarang : 36
Catatan terakhir: Jum 29.3Tiga lompatan pada program itu mewakili ketiga asal fseek. Lompatan pertama menuju offset 18 mengambil catatan ketiga tanpa menyentuh dua catatan di depannya. Lompatan kedua menunjukkan bahwa SEEK_CUR menghitung dari posisi terkini, sehingga sisa baris m 27.8 ikut terbaca. Lompatan ketiga menggunakan offset negatif dari SEEK_END untuk mendarat tepat pada catatan terakhir.
Pola perhitungan 9 * i berlaku untuk catatan berukuran sama apa pun lebarnya, dan kebiasaan menyusun catatan berukuran tetap inilah yang membuat fseek bekerja dengan mudah. Diagram berikut memetakan posisi-posisi itu pada berkas 45 byte:
Menentukan ukuran berkas dengan fseek dan ftell#
Ukuran berkas dapat ditentukan tanpa membacanya, yaitu dengan memindahkan penunjuk ke ujung berkas lalu membaca posisinya. Idiomnya terdiri atas tiga langkah, yaitu fseek(stream, 0L, SEEK_END), memanggil ftell untuk mendapatkan ukuran dalam byte, lalu rewind untuk mengembalikan penunjuk ke awal. Fungsi rewind bekerja seperti fseek(stream, 0L, SEEK_SET), tetapi tanpa nilai kembali. Panggilannya sekalian menghapus indikator akhir berkas serta indikator error, sehingga stream siap dibaca ulang dari awal. Detail indikator-indikator itu dibahas pada topik Penanganan Error Berkas:
#include <stdio.h>
int main(void) {
FILE *berkas = fopen("sensor.txt", "r");
if (berkas == NULL) {
perror("fopen sensor.txt");
return 1;
}
if (fseek(berkas, 0L, SEEK_END) != 0) {
perror("fseek sensor.txt");
fclose(berkas);
return 1;
}
long ukuran = ftell(berkas);
rewind(berkas);
printf("Ukuran sensor.txt: %ld byte\n", ukuran);
printf("Posisi kembali ke: %ld\n", ftell(berkas));
fclose(berkas);
return 0;
}Ukuran sensor.txt: 45 byte
Posisi kembali ke: 0Hasilnya cocok dengan hitungan manual, yaitu lima catatan dikali 9 byte. Teknik ini nyaman untuk berkas biasa, tetapi tipe long pada beberapa platform hanya mampu menampung hingga sekitar dua miliar byte. Berkas yang jauh lebih besar membutuhkan tipe lebar lebih seperti off_t beserta fungsi POSIX ftello dan fseeko. Angka ukuran semacam ini juga berguna untuk menyiapkan buffer sebesar berkas sebelum dibaca sekaligus.
Ukuran yang diketahui lebih dahulu mendukung pola baca sekaligus, yaitu menyediakan buffer sebesar berkas lalu menyeret seluruh isinya dengan satu fread. Pola ini hemat waktu untuk berkas kecil dan menengah, tetapi memorinya ikut terpakai sebesar ukuran berkas, sehingga berkas ratusan megabyte sebaiknya tetap diproses per bagian. Penentuannya sederhana, yaitu bandingkan ukuran berkas dengan batas memori yang wajar bagi program, kemudian pilih pola baca yang sesuai.
Kesalahan umum#
Kesalahan pertama adalah menghitung offset pada berkas mode teks. Standar C hanya menjamin penghitungan offset yang akurat pada berkas biner. Mode teks pada beberapa platform menerjemahkan pindah baris, sehingga hitungan byte tidak lagi cocok dengan isi berkas. Gunakan mode biner sejak awal apabila berkasnya akan dilompati dengan hitungan byte.
Kesalahan kedua adalah membiarkan nilai kembali fseek tanpa diperiksa. Lompatan ke offset negatif sebelum awal berkas akan gagal dengan nilai kembali -1, dan program yang tidak memeriksanya justru membaca data pada posisi lama seolah-olah lompatannya berhasil. Kesalahan ketiga adalah menampung hasil ftell dalam int. Ukuran berkas di atas dua miliar byte terpangkas menjadi nilai negatif pada tipe itu, sehingga tipe long dipertahankan sesuai nilai kembali ftell. Kesalahan keempat adalah mengharapkan fseek memindahkan titik tulis pada mode "a", padahal penulisan selalu menuju ujung berkas berapa pun posisi bacanya.
Ringkasan#
- Setiap stream memiliki penunjuk posisi berupa nomor byte berikutnya, dan ftell membacanya sebagai nilai
long. - fseek memindahkan penunjuk dari
SEEK_SET,SEEK_CUR, atauSEEK_END, dengan nilai kembali0saat berhasil. - Catatan berukuran tetap memungkinkan penghitungan offset langsung, sehingga catatan ke-i diambil tanpa membaca bagian sebelumnya.
- Ukuran berkas diperoleh dari ftell setelah lompatan
SEEK_END, lalu rewind mengembalikan penunjuk ke awal sekaligus membersihkan indikator.
Referensi#
- fseek — cppreference — Dokumentasi fseek beserta ketiga konstanta asal pencarian.
- ftell — cppreference — Dokumentasi ftell dan rewind pada cppreference.
- fseek(3) — Linux manual pages — Man page fseek, ftell, dan rewind di Linux.