Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 18.626 phút đọc

Truy cập ngẫu nhiên với fseek và ftell

Sau bài này bạn sẽ làm được

  • Dùng fseek với ba mốc SEEK_SET, SEEK_CUR và SEEK_END
  • Tính vị trí bản ghi thứ i và cập nhật nó tại chỗ
  • Nêu quy tắc phải gọi fseek hoặc fflush khi chuyển giữa đọc và ghi
  • Biết giới hạn của ftell và khi nào cần fseeko

Tới đây bạn mới chỉ đọc file từ đầu tới cuối. Hai hàm trong bài này mở ra khả năng thứ hai: nhảy thẳng tới byte thứ một triệu, đọc một bản ghi, sửa nó, và ghi đè lại đúng chỗ cũ. Đây là điều mà file văn bản không làm được.

#fseek và ba mốc

int fseek(FILE *f, long do_lech, int moc);

/* moc: SEEK_SET (dau tep) · SEEK_CUR (vi tri hien tai) · SEEK_END (cuoi tep)
   Tra ve 0 neu thanh cong, khac 0 neu that bai. */
Độ lệch được tính từ mốc bạn chọn, và nó âm được với SEEK_CUR cùng SEEK_END.
Lời gọiKết quảThường dùng khi
fseek(f, 0, SEEK_SET)Về đầu tệpĐọc lại từ đầu, giống rewind
fseek(f, 0, SEEK_END)Tới cuối tệpĐo kích thước, hoặc ghi thêm
fseek(f, -1, SEEK_END)Byte cuối cùngKiểm tra tệp có kết thúc bằng xuống dòng không
fseek(f, 12, SEEK_SET)Bỏ qua phần đầu 12 byteNhảy qua phần đầu tệp tới vùng dữ liệu
fseek(f, -4, SEEK_CUR)Lùi 4 byteĐọc rồi muốn đọc lại

#ftell và kích thước file

long ftell(FILE *f);     /* vi tri hien tai, tinh bang byte tu dau tep
                            hoac -1L neu that bai */
Đo kích thước một tệp
long kich_thuoc(FILE *f) {
    long cu = ftell(f);                        /* nho vi tri dang o */
    if (cu < 0) return -1;

    if (fseek(f, 0, SEEK_END) != 0) return -1;

    long co = ftell(f);

    if (fseek(f, cu, SEEK_SET) != 0) return -1; /* tra lai cho cu */

    return co;
}

#Truy cập bản ghi thứ i

Với một triệu bản ghi, chênh lệch là một triệu lần ghi so với một lần ghi.
/* Vi tri cua ban ghi thu i, khi moi ban ghi co cung kich thuoc: */

long vi_tri = CO_PHAN_DAU + (long)i * (long)sizeof(BanGhi);
ban-ghi.c
#include <stdio.h>
#include <string.h>

typedef struct { char ma[8]; int diem; } BG;

/* Doc ban ghi thu i */
int doc_ban_ghi(FILE *f, size_t i, BG *ra) {
    if (fseek(f, (long)(i * sizeof *ra), SEEK_SET) != 0) return -1;

    return fread(ra, sizeof *ra, 1, f) == 1 ? 0 : -1;
}

/* Ghi de ban ghi thu i, khong dung toi cac ban ghi khac */
int ghi_ban_ghi(FILE *f, size_t i, const BG *bg) {
    if (fseek(f, (long)(i * sizeof *bg), SEEK_SET) != 0) return -1;

    return fwrite(bg, sizeof *bg, 1, f) == 1 ? 0 : -1;
}

/* Dem so ban ghi tu kich thuoc tep */
long so_ban_ghi(FILE *f) {
    if (fseek(f, 0, SEEK_END) != 0) return -1;

    long co = ftell(f);
    if (co < 0) return -1;

    return co / (long)sizeof(BG);
}
terminal
./sua-tai-cho
sizeof(BG) = 12
truoc:
  [0] SV000    50
  [1] SV001    51
  [2] SV002    52
  [3] SV003    53
  [4] SV004    54

fseek toi vi tri 24
doc duoc: SV002 52, ftell gio la 36

sau:
  [0] SV000    50
  [1] SV001    51
  [2] SV002    99
  [3] SV003    53
  [4] SV004    54

kich thuoc 60 byte -> 5 ban ghi

#Quy tắc khi vừa đọc vừa ghi

#Giới hạn của long

Nền tảngsizeof(long)Tệp lớn nhất fseek xử lý được
Linux 64 bit8 byteKhoảng 9,2 tỉ gigabyte, không phải lo
Windows 64 bit4 byte2 GB
Linux 32 bit4 byte2 GB
Nhúng 32 bit4 byte2 GB
Windows giữ long ở 4 byte kể cả trên bản 64 bit, nên đây không phải chuyện của quá khứ.
/* Tren POSIX, dung ban co off_t: */
#define _FILE_OFFSET_BITS 64
#include <stdio.h>

int   fseeko(FILE *f, off_t do_lech, int moc);
off_t ftello(FILE *f);

/* Tren Windows, Microsoft co ban rieng: */
int   _fseeki64(FILE *f, __int64 do_lech, int moc);
__int64 _ftelli64(FILE *f);

/* Trong chuan C co cap fgetpos va fsetpos, kha chuyen nhung han che: */
int fgetpos(FILE *f, fpos_t *vi_tri);
int fsetpos(FILE *f, const fpos_t *vi_tri);

/* fpos_t la kieu mo. Ban KHONG cong tru duoc voi no,
   chi luu lai roi quay ve dung cho cu. Du de danh dau
   va quay lai, khong du de tinh vi tri ban ghi thu i. */

Tự làm thử

  1. Tạo tệp năm bản ghi, sửa bản ghi thứ hai tại chỗ, và in ra cả năm để xác nhận bốn bản ghi kia không đổi.
  2. In ftell ngay sau fread và giải thích vì sao nó bằng vị trí bắt đầu cộng kích thước bản ghi.
  3. Bỏ lời gọi fseek giữa fread và fwrite rồi xem bản ghi nào bị sửa.
  4. Mở một tệp văn bản bằng cả "r" và "rb", so sánh giá trị ftell ở cuối tệp với số byte fread đọc được.
  5. Ghi bản ghi thứ mười vào tệp chỉ có năm bản ghi, rồi đọc lại bản ghi thứ bảy xem nó chứa gì.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • fseek nhận độ lệch tính từ một trong ba mốc, và độ lệch âm được với SEEK_CUR cùng SEEK_END.
  • Vị trí bản ghi thứ i là co_phan_dau + i * sizeof(BanGhi), và nhờ đó sửa một bản ghi chỉ tốn một lần ghi.
  • Sau fread thì con trỏ đã tiến lên, nên phải fseek lại trước khi ghi đè.
  • Với chế độ có dấu cộng, phải xen fseek giữa đọc và ghi, và fseek(f, 0, SEEK_CUR) là cách gọn nhất.
  • ftell chỉ cho số byte thật ở chế độ nhị phân, và nó giới hạn ở 2 GB trên mọi nền tảng có long bốn byte, kể cả Windows 64 bit.