x9nd
05-23-2009, 05:18 PM
Bài 20 : Quản lư tập tin
Giới thiệu
- Hầu hết các chương tŕnh đều yêu cầu đọc và ghi dữ liệu vào các hệ thống lưu trữ trên đĩa. Các chương tŕnh xử lư văn bản cần lưu các tập tin văn bản, chương tŕnh xử lư bảng tính cần luu nội dung của các ô, chương tŕnh cơ sở dữ liệu cần lưu các mẩu tin. Bài này sẽ khám phá các tiện ích trong C dành cho các thao tác nhập/xuất (I/O) đĩa hệ thống.
- Ngôn ngữ C không chứa bắt kỳ câu lệnh nhập/xuất nào một cách tường minh. Tất cả các thao tác nhập/xuất đều thực hiện thông qua các hàm thư viện chuẩn của C. Tiếp cận này làm cho hệ thống quản lư tập tin của C rất mạnh và uyển chuyển. Nhập/xuất trong C là tuyệt vời v́ dữ liệu có thể truyền ở dạng nhị phân hay ở dạng văn bản mà con người có thể đọc được. Điều này làm cho việc tạo tập tin để đáp ứng mọi nhu cầu một cách dễ dàng.
- Việc hiểu rơ sự khác biệt giữa stream và tập tin là rất quan trọng. Hệ thống nhập/xuất của C cung cấp cho người dùng một giao diện độc lập với thiết bị thật sự đang truy cập. Giao diện này không phải là một tập tin thật sự mà là một sự biểu diễn trừu tượng hóa của thiết bị. Giao diện trừu tượng hóa này được gọi là một stream và thiết bị thật sự được gọi là tập tin.
1. File Streams
- Hệ thống tập tin của C làm việc được với rất nhiều thiết bị khác nhau bao gồm máy in, ổ đĩa, ổ băng từ và các thiết bị đầu cuối. Mặc dù tất cả các thiết bị đều khác nhau, nhưng hệ thống tập tin có vùng đệm sẽ chuyển mỗi thiết bị về một thiết bị logic gọi là một stream. V́ mọi stream hoạt động tương tự, nên việc quản lư các thiết bị là rất dễ dàng. Có hai loại stream - văn bản (text) và nhị phân (binary).
2. Streams văn bản
- Một streams văn bản là một chuỗi các kư tự. Các streams văn bản có thể được tổ thành các ḍng, mỗi ḍng kết thúc bằng một kư tự sang ḍng mới. Tuy nhiên, kư tự sang ḍng mới là tùy chọn trong ḍng cuối và được quyết định khi cài đặt. Hầu hết các tŕnh biên dịch C không kết thúc stream văn bản với kư tự sang ḍng mới. Trong một stream văn bản, có thể xảy ra một vài sự chuyển đổi kư tự khi môi trường yêu cầu. Chẳng hạn như, kư tự sang ḍng mới có thể được chuyển thành một cặp kư tự về đầu ḍng/nhảy đến ḍng kế. V́ vậy, mối quan hệ giữa các kư tự được ghi (hay đọc) và những kư tự ở thiết bị ngoại vi có thể không phải là mối quan hệ một-một. Và cũng v́ sự chuyển đổi có thể xảy ra này, số lượng kư tự được ghi (hay đọc) có thể không giống như số lượng kư tự nh́n thấy ở thiết bị ngoại vi.
3. Stream nhị phân
- Một stream nhị phân là một chuỗi các byte với sự tương ứng một-một với thiết bị ngoại vi, nghĩa là, không có sự chuyển đổi kư tự. Cũng v́ vậy, số lượng byte đọc (hay ghi) cũng sẽ giống như số lượng byte ở thiết bị ngoại vi. Các stream nhị phân là các chuỗi byte thuần túy, mà không có bất kỳ kư hiệu nào được dùng để chỉ ra điểm kết thúc của tập tin hay kết thúc của record. Kết thúc của tập tin được xác định bằng độ lớn của tập tin.
4. Các hàm về tập tin và structure FILE
- Một tập tin có thể tham chiếu đến bất cứ cái ǵ, từ một tập tin trên đĩa đến một thiết bị đầu cuối hay một mày in. Tuy nhiên, tất cả các tập tin đều không có cùng khả năng. Ví dụ như, một tập tin trên đĩa có thể hỗ trợ truy cập ngẫu nhiên trong khi một bàn phím th́ không. Một tập tin sẽ kết hợp với một stream bằng cách thực hiện thao tác mở. Tương tự, nó sẽ thôi kết hợp với một stream bằng thao tác đóng. Khi một chương tŕnh kết thúc b́nh thường, tất cả các tập tin đều tự động đóng. Tuy nhiên, khi một chương tŕnh bị treo hoặc kết thúc bất thường, các tập tin vẫn c̣n mở.
5. Các hàm cơ bản về tập tin.
- Một hệ thống quản lư tập tin theo chuẩn ANSI bao gồm một số hàm liên quan với nhau. Các hàm thông dụng nhất được liệt kê trong bảng dưới đây.
<picture>
- Các hàm trên chứa trong tập tin header stdio.h. Tập tin header này phải bao gồm vào chương tŕnh có sử dụng các hàm này. Hầu hết các hàm này tương tự như các hàm nhập/xuất từ thiết bị nhập xuất chuẩn. Tập tin header stdio.h c̣n định nghĩa một số macro sử dụng trong quá tŕnh xử lư tập tin. Ví dụ như macro EOF được định nghĩa là -1, chứa giá trị trả về khi một hàm cố đọc tiếp khi đă đến cuối tập tin.
6. Con trỏ tập tin.
- Một con trỏ tập tin (file pointer) rất cần thiết cho việc đọc xuất và ghi các tập tin. Nó là một con trỏ đến một structure chứa thống tin vê tập tin. Thông tin bao gồm: tên tập tin, vị trí hiện tại của tập tin, tập tin đang được đọc hay ghi, có bất kỳ lỗi nào xuất hiện đă đến cuối tập tin. Người dùng không cần thiết phải biết chi tiết, v́ các định nghĩa lấy từ studio.h có bao gồm một khai báo structure tên là FILE. Câu lệnh khai báo duy nhất cần thiết cho một con trỏ tập tin là:
Code:
FILE *fp;
- Khai báo này cho biết fp là một con trỏ đến một FILE.
7. Mở một tập tin văn bản
- Hàm fopen() mở một stream để sử dụng và liên kết một tập tin với stream đó. Con trỏ kết hợp với tập tin được trả về từ hàm fopen(). Trong hầu hết các trường hợp, tập tin đang mở là một tập tin trên đĩa. Nguyên mẫu hàm fopen() là:
Code:
FILE * fopen(const char *filename, const char *mode);
- Trong đó filename là một con trỏ đến chuỗi kư tự chứa một tên tập tin hợp lệ và cũng có thể chứa cả phần mô tả đường dẫn. Chuỗi được trỏ đến bởi con trỏ mode xác định cách thức tập tin được mở. Bảng sau liệt kê một số chế độ mở của tập tin.
<picture>
- Macro NULL được định nghĩa trong stdio.h là "\0". Nếu sử dụng phương phát trên để mở tập tin, th́ hàm fopen() sẽ phát hiện ra lỗi nếu có, chảng hạn như đĩa đang ở chế bộ cấm ghi (write-protected) hay đĩa đầy, trước khi bắt đầu ghi đĩa.
- Nếu một tập tin được mở để ghi, bất kỳ một tập tin nào có cùng tên và đang mở sẽ bị viết chồng lên. V́ khi một tập tin được mở ở chế độ ghi, th́ một tập tin mới được tạo ra. Nếu muốn nối thêm các mẫu tin vào tập tin đă có, th́ nó phải được mở với chế độ "a". Nếu một tập tin được mở ở chế độ đọc và nó không tồn tại, hàm sẽ trả về lỗi. Nếu một tập tin được mở để đọc/ghi, nó sẽ không bị xóa nếu đă tồn tại, hàm sẽ trả về lỗi. Nếu một tập tin được mở ra để đọc/ghi, nó sẽ không bị xóa nếu đă tồn tại. Tuy nhiên, nếu nó không tồn tại, th́ nó sẽ được tạo ra. Theo chuẩn ANSI, tám tập tin có thể được mở tại một thời điểm. Tuy vậy, hầu hết các tŕnh biên dịch C và môi trường đều cho phép mở nhiều hơn tám tập tin.
8. Đóng một tập tin văn bản
- V́ số lượng tập tin có thể mở tại một thời điểm bị giới hạn, việc đóng một tập tin khi không c̣n sử dụng là một điều quan trọng. Thao tác này sẽ giải phóng tài nguyên và làm giảm nguy cơ vượt quá giới hạn đă định. Đóng một stream cũng sẽ làm sạch và chép vùng đệm kết hợp của nó ra ngoài (một thao tác quan trọng để tránh mất dữ liệu) khi ghi ra đĩa. Hàm fclose() đóng một stream đă được mở bằng hàm fopen(). Nó ghi bất kỳ dữ liệu nào c̣n lại trong vùng đệm của đĩa vào tập tin. Nguyên mẫu của hàm fopen() là:
Code:
int fclose(FILE *fp);
- Trong đó fp là một con trỏ tập tin. Hàm fclose() trả về 0 nếu đóng thành công. Bất kỳ giá trị trả về nào khác 0 đều cho thấy có lỗi xảy ra. Hàm fclose() sẽ thất bại nếu đĩa đă sớm được gỡ ra khỏi ổ đĩa hoặc đĩa bị đẩy.
- Một hàm khác dùng để dóng stream là hàm fcloseall(). Hàm này hữu dụng khi phải đóng cùng một lúc nhiều stream đang mở. Nó sẽ đóng tất cả các stream và trả về số stream đă đóng hoặc EOF nếu có phát hiện lỗi. Nó có thể được sử dụng theo cách như sau:
Code:
flc = fcloseall();
if(fcl==EOF
{
printf("Error closing files");
}
else
printf("%d file(s) closed", fcl);
9. Ghi một kư tự
- Stream có thể được ghi vào tập tin theo từng kư tự một hoặc theo từng chuỗi. Trước hết chúng ta hăy thảo luận về cách ghi các kư tự vào tập tin. Hàm fputc() được sử dụng để ghi các kư tự vào tập tin đă được mở trước đó bằng hàm fopen().. Nguyên mẫu hàm của hàm này như sau:
Code:
int fputc(int ch, FILE *fp)
- Trong đó fp là một con trỏ tập tin trả về bởi hàm fopen() và ch là kư tự cần ghi. Mặc dù ch được khai báo là kiểu int, nhưng nó được hàm fputc() chuyển đổi thành kiểu unsigned char. Hàm fputc() ghi một kư tự vào stream đă định vị trí hiện hành của con trỏ định vị trí bên trong tập tin và sau đó tăng con trỏ này lên. Nếu fputc() thành công, nó trả về kư tự đă ghi, ngược lại nó trả về EOF.
10. Đọc một kư tự
- Hàm fgetc() được dùng để đọc các kư tự từ một tập tin đă được mở ở chế độ đọc, sử dụng hàm fopen(). Nguyễn mẫu của hàm là:
Code:
int fgetc(FILE *fp);
- Trong đó fp là một con trỏ tập tin kiểu FILE trả về bởi hàm fopen(). Hàm fgetc() trả về kư tự kế tiếp của vị trí hiện hành trong stream input, và tăng con trỏ định vị trí bên trong tập tin lên. Kư tự đọc được là một kư tự kiểu unsigned char và được chuyển thành kiểu int. Nếu đă đên cuối tập tin, fgetc() trả về EOF. Để đọc một tập tin văn bản từ đầu cho đến cuối, câu lệnh sẽ là:
Code:
do
{
ch = fgetc(fp);
} while (ch!=EOF);
Giới thiệu
- Hầu hết các chương tŕnh đều yêu cầu đọc và ghi dữ liệu vào các hệ thống lưu trữ trên đĩa. Các chương tŕnh xử lư văn bản cần lưu các tập tin văn bản, chương tŕnh xử lư bảng tính cần luu nội dung của các ô, chương tŕnh cơ sở dữ liệu cần lưu các mẩu tin. Bài này sẽ khám phá các tiện ích trong C dành cho các thao tác nhập/xuất (I/O) đĩa hệ thống.
- Ngôn ngữ C không chứa bắt kỳ câu lệnh nhập/xuất nào một cách tường minh. Tất cả các thao tác nhập/xuất đều thực hiện thông qua các hàm thư viện chuẩn của C. Tiếp cận này làm cho hệ thống quản lư tập tin của C rất mạnh và uyển chuyển. Nhập/xuất trong C là tuyệt vời v́ dữ liệu có thể truyền ở dạng nhị phân hay ở dạng văn bản mà con người có thể đọc được. Điều này làm cho việc tạo tập tin để đáp ứng mọi nhu cầu một cách dễ dàng.
- Việc hiểu rơ sự khác biệt giữa stream và tập tin là rất quan trọng. Hệ thống nhập/xuất của C cung cấp cho người dùng một giao diện độc lập với thiết bị thật sự đang truy cập. Giao diện này không phải là một tập tin thật sự mà là một sự biểu diễn trừu tượng hóa của thiết bị. Giao diện trừu tượng hóa này được gọi là một stream và thiết bị thật sự được gọi là tập tin.
1. File Streams
- Hệ thống tập tin của C làm việc được với rất nhiều thiết bị khác nhau bao gồm máy in, ổ đĩa, ổ băng từ và các thiết bị đầu cuối. Mặc dù tất cả các thiết bị đều khác nhau, nhưng hệ thống tập tin có vùng đệm sẽ chuyển mỗi thiết bị về một thiết bị logic gọi là một stream. V́ mọi stream hoạt động tương tự, nên việc quản lư các thiết bị là rất dễ dàng. Có hai loại stream - văn bản (text) và nhị phân (binary).
2. Streams văn bản
- Một streams văn bản là một chuỗi các kư tự. Các streams văn bản có thể được tổ thành các ḍng, mỗi ḍng kết thúc bằng một kư tự sang ḍng mới. Tuy nhiên, kư tự sang ḍng mới là tùy chọn trong ḍng cuối và được quyết định khi cài đặt. Hầu hết các tŕnh biên dịch C không kết thúc stream văn bản với kư tự sang ḍng mới. Trong một stream văn bản, có thể xảy ra một vài sự chuyển đổi kư tự khi môi trường yêu cầu. Chẳng hạn như, kư tự sang ḍng mới có thể được chuyển thành một cặp kư tự về đầu ḍng/nhảy đến ḍng kế. V́ vậy, mối quan hệ giữa các kư tự được ghi (hay đọc) và những kư tự ở thiết bị ngoại vi có thể không phải là mối quan hệ một-một. Và cũng v́ sự chuyển đổi có thể xảy ra này, số lượng kư tự được ghi (hay đọc) có thể không giống như số lượng kư tự nh́n thấy ở thiết bị ngoại vi.
3. Stream nhị phân
- Một stream nhị phân là một chuỗi các byte với sự tương ứng một-một với thiết bị ngoại vi, nghĩa là, không có sự chuyển đổi kư tự. Cũng v́ vậy, số lượng byte đọc (hay ghi) cũng sẽ giống như số lượng byte ở thiết bị ngoại vi. Các stream nhị phân là các chuỗi byte thuần túy, mà không có bất kỳ kư hiệu nào được dùng để chỉ ra điểm kết thúc của tập tin hay kết thúc của record. Kết thúc của tập tin được xác định bằng độ lớn của tập tin.
4. Các hàm về tập tin và structure FILE
- Một tập tin có thể tham chiếu đến bất cứ cái ǵ, từ một tập tin trên đĩa đến một thiết bị đầu cuối hay một mày in. Tuy nhiên, tất cả các tập tin đều không có cùng khả năng. Ví dụ như, một tập tin trên đĩa có thể hỗ trợ truy cập ngẫu nhiên trong khi một bàn phím th́ không. Một tập tin sẽ kết hợp với một stream bằng cách thực hiện thao tác mở. Tương tự, nó sẽ thôi kết hợp với một stream bằng thao tác đóng. Khi một chương tŕnh kết thúc b́nh thường, tất cả các tập tin đều tự động đóng. Tuy nhiên, khi một chương tŕnh bị treo hoặc kết thúc bất thường, các tập tin vẫn c̣n mở.
5. Các hàm cơ bản về tập tin.
- Một hệ thống quản lư tập tin theo chuẩn ANSI bao gồm một số hàm liên quan với nhau. Các hàm thông dụng nhất được liệt kê trong bảng dưới đây.
<picture>
- Các hàm trên chứa trong tập tin header stdio.h. Tập tin header này phải bao gồm vào chương tŕnh có sử dụng các hàm này. Hầu hết các hàm này tương tự như các hàm nhập/xuất từ thiết bị nhập xuất chuẩn. Tập tin header stdio.h c̣n định nghĩa một số macro sử dụng trong quá tŕnh xử lư tập tin. Ví dụ như macro EOF được định nghĩa là -1, chứa giá trị trả về khi một hàm cố đọc tiếp khi đă đến cuối tập tin.
6. Con trỏ tập tin.
- Một con trỏ tập tin (file pointer) rất cần thiết cho việc đọc xuất và ghi các tập tin. Nó là một con trỏ đến một structure chứa thống tin vê tập tin. Thông tin bao gồm: tên tập tin, vị trí hiện tại của tập tin, tập tin đang được đọc hay ghi, có bất kỳ lỗi nào xuất hiện đă đến cuối tập tin. Người dùng không cần thiết phải biết chi tiết, v́ các định nghĩa lấy từ studio.h có bao gồm một khai báo structure tên là FILE. Câu lệnh khai báo duy nhất cần thiết cho một con trỏ tập tin là:
Code:
FILE *fp;
- Khai báo này cho biết fp là một con trỏ đến một FILE.
7. Mở một tập tin văn bản
- Hàm fopen() mở một stream để sử dụng và liên kết một tập tin với stream đó. Con trỏ kết hợp với tập tin được trả về từ hàm fopen(). Trong hầu hết các trường hợp, tập tin đang mở là một tập tin trên đĩa. Nguyên mẫu hàm fopen() là:
Code:
FILE * fopen(const char *filename, const char *mode);
- Trong đó filename là một con trỏ đến chuỗi kư tự chứa một tên tập tin hợp lệ và cũng có thể chứa cả phần mô tả đường dẫn. Chuỗi được trỏ đến bởi con trỏ mode xác định cách thức tập tin được mở. Bảng sau liệt kê một số chế độ mở của tập tin.
<picture>
- Macro NULL được định nghĩa trong stdio.h là "\0". Nếu sử dụng phương phát trên để mở tập tin, th́ hàm fopen() sẽ phát hiện ra lỗi nếu có, chảng hạn như đĩa đang ở chế bộ cấm ghi (write-protected) hay đĩa đầy, trước khi bắt đầu ghi đĩa.
- Nếu một tập tin được mở để ghi, bất kỳ một tập tin nào có cùng tên và đang mở sẽ bị viết chồng lên. V́ khi một tập tin được mở ở chế độ ghi, th́ một tập tin mới được tạo ra. Nếu muốn nối thêm các mẫu tin vào tập tin đă có, th́ nó phải được mở với chế độ "a". Nếu một tập tin được mở ở chế độ đọc và nó không tồn tại, hàm sẽ trả về lỗi. Nếu một tập tin được mở để đọc/ghi, nó sẽ không bị xóa nếu đă tồn tại, hàm sẽ trả về lỗi. Nếu một tập tin được mở ra để đọc/ghi, nó sẽ không bị xóa nếu đă tồn tại. Tuy nhiên, nếu nó không tồn tại, th́ nó sẽ được tạo ra. Theo chuẩn ANSI, tám tập tin có thể được mở tại một thời điểm. Tuy vậy, hầu hết các tŕnh biên dịch C và môi trường đều cho phép mở nhiều hơn tám tập tin.
8. Đóng một tập tin văn bản
- V́ số lượng tập tin có thể mở tại một thời điểm bị giới hạn, việc đóng một tập tin khi không c̣n sử dụng là một điều quan trọng. Thao tác này sẽ giải phóng tài nguyên và làm giảm nguy cơ vượt quá giới hạn đă định. Đóng một stream cũng sẽ làm sạch và chép vùng đệm kết hợp của nó ra ngoài (một thao tác quan trọng để tránh mất dữ liệu) khi ghi ra đĩa. Hàm fclose() đóng một stream đă được mở bằng hàm fopen(). Nó ghi bất kỳ dữ liệu nào c̣n lại trong vùng đệm của đĩa vào tập tin. Nguyên mẫu của hàm fopen() là:
Code:
int fclose(FILE *fp);
- Trong đó fp là một con trỏ tập tin. Hàm fclose() trả về 0 nếu đóng thành công. Bất kỳ giá trị trả về nào khác 0 đều cho thấy có lỗi xảy ra. Hàm fclose() sẽ thất bại nếu đĩa đă sớm được gỡ ra khỏi ổ đĩa hoặc đĩa bị đẩy.
- Một hàm khác dùng để dóng stream là hàm fcloseall(). Hàm này hữu dụng khi phải đóng cùng một lúc nhiều stream đang mở. Nó sẽ đóng tất cả các stream và trả về số stream đă đóng hoặc EOF nếu có phát hiện lỗi. Nó có thể được sử dụng theo cách như sau:
Code:
flc = fcloseall();
if(fcl==EOF
{
printf("Error closing files");
}
else
printf("%d file(s) closed", fcl);
9. Ghi một kư tự
- Stream có thể được ghi vào tập tin theo từng kư tự một hoặc theo từng chuỗi. Trước hết chúng ta hăy thảo luận về cách ghi các kư tự vào tập tin. Hàm fputc() được sử dụng để ghi các kư tự vào tập tin đă được mở trước đó bằng hàm fopen().. Nguyên mẫu hàm của hàm này như sau:
Code:
int fputc(int ch, FILE *fp)
- Trong đó fp là một con trỏ tập tin trả về bởi hàm fopen() và ch là kư tự cần ghi. Mặc dù ch được khai báo là kiểu int, nhưng nó được hàm fputc() chuyển đổi thành kiểu unsigned char. Hàm fputc() ghi một kư tự vào stream đă định vị trí hiện hành của con trỏ định vị trí bên trong tập tin và sau đó tăng con trỏ này lên. Nếu fputc() thành công, nó trả về kư tự đă ghi, ngược lại nó trả về EOF.
10. Đọc một kư tự
- Hàm fgetc() được dùng để đọc các kư tự từ một tập tin đă được mở ở chế độ đọc, sử dụng hàm fopen(). Nguyễn mẫu của hàm là:
Code:
int fgetc(FILE *fp);
- Trong đó fp là một con trỏ tập tin kiểu FILE trả về bởi hàm fopen(). Hàm fgetc() trả về kư tự kế tiếp của vị trí hiện hành trong stream input, và tăng con trỏ định vị trí bên trong tập tin lên. Kư tự đọc được là một kư tự kiểu unsigned char và được chuyển thành kiểu int. Nếu đă đên cuối tập tin, fgetc() trả về EOF. Để đọc một tập tin văn bản từ đầu cho đến cuối, câu lệnh sẽ là:
Code:
do
{
ch = fgetc(fp);
} while (ch!=EOF);