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ời gọi | Kế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ùng | Kiể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 byte | Nhả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
/* 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ảng | sizeof(long) | Tệp lớn nhất fseek xử lý được |
|---|---|---|
| Linux 64 bit | 8 byte | Khoảng 9,2 tỉ gigabyte, không phải lo |
| Windows 64 bit | 4 byte | 2 GB |
| Linux 32 bit | 4 byte | 2 GB |
| Nhúng 32 bit | 4 byte | 2 GB |
/* 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ử
- 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.
- In
ftellngay saufreadvà giải thích vì sao nó bằng vị trí bắt đầu cộng kích thước bản ghi. - Bỏ lời gọi
fseekgiữafreadvàfwriterồi xem bản ghi nào bị sửa. - 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ố bytefreadđọc được. - 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
fseeknhận độ lệch tính từ một trong ba mốc, và độ lệch âm được vớiSEEK_CURcùngSEEK_END.- Vị trí bản ghi thứ
ilàco_phan_dau + i * sizeof(BanGhi), và nhờ đó sửa một bản ghi chỉ tốn một lần ghi. - Sau
freadthì con trỏ đã tiến lên, nên phảifseeklại trước khi ghi đè. - Với chế độ có dấu cộng, phải xen
fseekgiữa đọc và ghi, vàfseek(f, 0, SEEK_CUR)là cách gọn nhất. ftellchỉ 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ólongbốn byte, kể cả Windows 64 bit.