Đọc cả file vào bộ nhớ
Sau bài này bạn sẽ làm được
- Viết hàm đọc cả file xử lý đủ mọi nhánh lỗi
- Giải thích vì sao ftell không cho kích thước đúng ở chế độ văn bản
- Viết bản đọc theo khối tăng dần, chạy được với ống dẫn
- Quyết định khi nào nên đọc cả file và khi nào nên đọc theo dòng
Đọc trọn một file vào bộ nhớ rồi xử lý là cách viết gọn nhất và nhanh nhất cho phần lớn công việc. Hàm làm việc đó chỉ mười lăm dòng, nhưng bản viết vội có ba chỗ hỏng, và cả ba đều chỉ lộ ra khi gặp dữ liệu thật.
#Thành ngữ đọc cả file
#include <stdio.h>
#include <stdlib.h>
/* Doc toan bo mot tep vao bo nho.
Tra ve con tro toi vung da cap phat, hoac NULL neu that bai.
NGUOI GOI phai free ket qua.
Neu ra_co khac NULL thi so byte doc duoc duoc ghi vao do. */
char *doc_ca_file(const char *ten, size_t *ra_co) {
FILE *f = fopen(ten, "rb"); /* PHAI la "rb" */
if (!f) return NULL;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
long co = ftell(f);
if (co < 0) { fclose(f); return NULL; }
rewind(f);
char *bo_dem = malloc((size_t)co + 1); /* cong 1 cho ky tu ket thuc */
if (!bo_dem) { fclose(f); return NULL; }
size_t doc = fread(bo_dem, 1, (size_t)co, f);
if (ferror(f)) { free(bo_dem); fclose(f); return NULL; }
fclose(f);
bo_dem[doc] = '\0'; /* dung doc, KHONG dung co */
if (ra_co) *ra_co = doc;
return bo_dem;
}Mở bằng "rb", không phải "r"
Ở chế độ văn bản trên Windows,
ftellcho số byte trên đĩa cònfreadtrả về số ít hơn, vì mỗi cặp\r\nbị gộp lại. Bài 18.6 đã đo chênh lệch đó.Nhảy tới cuối, hỏi vị trí, quay về đầu
Ba lời gọi này là cách duy nhất mà chuẩn C cho phép đo kích thước một tệp. Không có hàm nào tên là
filesize.Cấp phát dư một byte
Byte thừa đó chứa ký tự kết thúc chuỗi, nhờ vậy kết quả dùng được ngay với
strstr,strtokvà mọi hàm chuỗi.Đặt ký tự kết thúc tại vị trí fread trả về
Không phải tại
co. Nếu hai số khác nhau thì phần giữa chúng là bộ nhớ chưa khởi tạo, và đọc nó là hành vi không xác định.
size_t co;
char *noi_dung = doc_ca_file("cau-hinh.txt", &co);
if (!noi_dung) { perror("cau-hinh.txt"); return 1; }
printf("Tep dai %zu byte\n", co);
if (strstr(noi_dung, "debug=1")) bat_go_loi();
free(noi_dung); /* DUNG QUEN */#Ba chỗ hỏng của nó
Hỏng một: rò rỉ ở mọi nhánh lỗi
char *doc_ca_file(const char *ten) {
FILE *f = fopen(ten, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long co = ftell(f);
rewind(f);
char *bo_dem = malloc(co + 1);
if (!bo_dem) return NULL; /* RO RI: f chua dong */
fread(bo_dem, 1, co, f);
bo_dem[co] = '\0';
return bo_dem; /* RO RI: f chua dong */
}/* Moi duong ra deu di qua fclose. Xem lai ban day du o tren:
moi lenh return deu co { fclose(f); return NULL; } */
/* Cach khac, dung goto khi co nhieu tai nguyen: */
char *doc_ca_file(const char *ten, size_t *ra_co) {
char *bo_dem = NULL;
FILE *f = fopen(ten, "rb");
if (!f) return NULL;
if (fseek(f, 0, SEEK_END) != 0) goto xong;
long co = ftell(f);
if (co < 0) goto xong;
rewind(f);
bo_dem = malloc((size_t)co + 1);
if (!bo_dem) goto xong;
size_t doc = fread(bo_dem, 1, (size_t)co, f);
if (ferror(f)) { free(bo_dem); bo_dem = NULL; goto xong; }
bo_dem[doc] = '\0';
if (ra_co) *ra_co = doc;
xong:
fclose(f);
return bo_dem;
}Hỏng hai: ftell thất bại với ống dẫn
Tep dai 1284 byte
loi: khong doc duoc
fseek(f, 0, SEEK_END); /* voi ong dan: TRA VE KHAC 0, that bai */
long co = ftell(f); /* hoac tra ve -1 */
/* Neu ban khong kiem tra thi:
co la -1
malloc(-1 + 1) tuc malloc(0), tra ve con tro hop le nhung 0 byte
fread ghi vao do -> TRAN BO DEM
Neu ban ep sang size_t:
(size_t)(-1) la 18446744073709551615
malloc that bai, tra NULL
may man hon, nhung van la loi. */Hỏng ba: tin vào kích thước mà không có giới hạn
long co = ftell(f);
char *bo_dem = malloc((size_t)co + 1);
/* Nguoi dung tro chuong trinh vao mot tep 40 GB.
malloc that bai, ban tra NULL, coi nhu on.
Nhung neu chuong trinh cua ban la mot dich vu mang
va co la 8 GB thi may chu cua ban vua bi chiem het RAM
boi mot yeu cau duy nhat. */
#define TOI_DA (64L * 1024 * 1024) /* 64 MB */
if (co > TOI_DA) {
fprintf(stderr, "%s: tep qua lon (%ld byte, toi da %ld)\n",
ten, co, TOI_DA);
fclose(f);
return NULL;
}#Bản không cần biết kích thước
#include <stdio.h>
#include <stdlib.h>
/* Doc ca luong, khong dung fseek nen chay duoc voi ONG DAN.
Nguoi goi phai free ket qua. */
char *doc_ca_luong(FILE *f, size_t *ra_co) {
size_t co = 0, suc_chua = 4096;
char *bo_dem = malloc(suc_chua);
if (!bo_dem) return NULL;
for (;;) {
if (co + 1 >= suc_chua) { /* chua cho ky tu ket thuc */
size_t moi_sc = suc_chua * 2;
char *moi = realloc(bo_dem, moi_sc);
if (!moi) { free(bo_dem); return NULL; }
bo_dem = moi;
suc_chua = moi_sc;
}
size_t n = fread(bo_dem + co, 1, suc_chua - co - 1, f);
co += n;
if (n == 0) break; /* het luong hoac loi */
}
if (ferror(f)) { free(bo_dem); return NULL; }
bo_dem[co] = '\0';
if (ra_co) *ra_co = co;
return bo_dem;
}doc duoc 20971520 byte
doc duoc 11 byte 20 byte dau: [mot hai ba ]
doc duoc 20971520 byte
#Đọc cả file hay đọc theo dòng
| Công việc | Nên dùng | Vì sao |
|---|---|---|
| Đếm dòng, đếm từ, tính tổng một cột | Đọc theo dòng | Bộ nhớ cố định, tệp lớn bao nhiêu cũng chạy |
| Tìm chuỗi có thể nằm vắt qua hai dòng | Đọc cả file | Theo dòng thì phải tự ghép, rất dễ sai |
| Phân tích cú pháp có cấu trúc lồng nhau | Đọc cả file | Bộ phân tích cần nhìn lùi và nhìn tới |
| Xử lý tệp nhật ký hàng gigabyte | Đọc theo dòng | Không có cách nào khác |
| Nạp tệp cấu hình lúc khởi động | Đọc cả file | Nhỏ, và mã gọn hơn hẳn |
| Đọc từ mạng hoặc ống dẫn không tin cậy | Theo dòng, hoặc cả file CÓ giới hạn | Bên kia có thể gửi vô hạn |
Tự làm thử
- Cài
doc_ca_fileđầy đủ và kiểm tra nó với tệp rỗng, tệp một byte và tệp không tồn tại. - Chạy nó với đầu vào qua ống dẫn và xác nhận nó báo lỗi thay vì cho kết quả sai.
- Cài
doc_ca_luongvới sức chứa ban đầu 16 byte để épreallocchạy nhiều lần, rồi kiểm tra kết quả vẫn đúng. - Thêm giới hạn kích thước và thử với một tệp vượt giới hạn.
- Viết hàm tách dòng tại chỗ và so sánh tốc độ với vòng
fgetstrên tệp 20 MB.
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
- Đo kích thước tệp bằng bộ ba
fseektới cuối,ftell, rồirewind, và tệp phải mở ở chế độ nhị phân. - Đặt ký tự kết thúc tại vị trí
freadtrả về, không phải tại kích thước đo được. - Mọi nhánh lỗi đều phải đóng tệp. Nhãn dọn dẹp ở cuối hàm cùng
gotolà mẫu chuẩn cho việc này. fseekthất bại với ống dẫn, nên bản đọc theo khối tăng dần mới là bản chạy được ở mọi nơi.- Luôn đặt giới hạn kích thước khi tệp do người ngoài cung cấp.