Xử lý dữ liệu tệp nhị phân - Lập trình C : Bài 19

Xử lý dữ liệu tệp nhị phân - Lập trình C : Bài 19
access_time 10/18/2019 12:00:00 AM
person Nguyễn Mạnh Hùng

Xử lý dữ liệu tệp nhị phân - Lập trình C : Bài 19

1. Giới thiệu

Các thao tác xử lý trên file nhị phân về cơ bản cũng tương tự như trên file văn bản vì cả 2 loại file đều có thể xem xét dưới dạng luồng dữ liệu (stream) chứa các bytes. Trong thực tế một số hàm truy cập file là giống nhau. Khi một file được mở, nó phải được chỉ định file mở là file văn bản hay file nhị phân và đây là dấu hiệu duy nhất để xác định kiểu file khi xử lý.

Để minh họa một file nhị phân, xem xét chương trình sau có chứa hàm filecopy(), hàm này thực hiện copy dữ liệu từ file nguồn sang file đích với 2 tham số hình thức có kiểu con trỏ * char. Nếu lỗi xuất hiện khi thực hiện open một trong 2 file thì giá trị trả về của hàm filecopy() là -1. Khi copy dữ liệu thành công, hàm sẽ đóng cả 2 file và giá trị trả về sẽ là 0.

Các bước sau mô tả copy dữ liệu 2 file nhị phân :

+ Mở file nguồn theo kiểu xử lý đọc gắn với file nhị phân “rb”.

+ Mở file nguồn theo kiểu xử lý ghi thông tin gắn với file nhị phân “wb”.

+ Đọc từng ký tự từ file nguồn, nhớ rằng khi lần đầu tiên file đó được mở, con trỏ sẽ bắt đầu làm việc với file. Mặc định vị trí con trỏ sẽ là vị trí đầu file, do vậy không cần phải xác định vị trí tường mình của con trỏ.

+ Sử dụng hàm feof() để xác định vị trí cuối cùng của file nguồn. Nếu kí tự đang đọc là không phải là kí tự đánh dấu cuối file thì thực hiện ghi nó vào file đích. Nếu kí tự đọc file là EOF thì kết thúc quá trình đọc file nguồn và ghi ở file đích. Cuối cùng là thực hiện đóng cả 2 file.

Code minh họa

int filecopy(char *s, char *d){

 FILE *ofp, *nfp;

 int ch;

 /* Mở file nguồn và đọc theo cơ chế xử lý của file nhị phân*/

 if((ofp = fopen(s, “rb”)) == NULL) {

          return -1;

 }

 /* Mở file đích và thực hiện ghi theo cơ chế file nhị phân */

 If ((nfp = fopen(d, “wb”)) == NULL){

          fclose(ofp);

          return -1;

  }

 while(1){

  ch = fgetc(ofp); // Đọc từng character của file nguồn

  if(!feof(ofp))

          fputc(ch, nfp); // Ghi character vào file đích

  else

          break;

  }

 fclose(nfp);

 fclose(ofp);

 return 0;

}

2. Vào/Ra trực tiếp với dữ liệu tệp

Vào/Ra (Input/Output) trực tiếp chỉ được sử dụng khi thao tác với file theo kiểu nhị phân. Với ra (output) trực tiếp, khác khối dữ liệu sẽ được ghi từ bộ nhớ vào ổ đĩa. Với tiến trình xử lý input quá trình xảy ra ngược lại với output. Một khối dữ liệu được đọc từ file vào bộ nhớ. Trong C cung cấp 2 hàm quan trọng để xử lý vào/ra trực tiếp đó là : fread() và fwrite(). Các hàm này có thể đọc/ghi bất kỳ kiểu dữ liệu nào.

Nguyên mẫu của 2 hàm :

size_t fread(void *buffer, size_t size, size_t num, FILE *fp);

size_t fwrite(void *buffer, size_t size, size_t num, FILE *fp);

Hàm fread đọc dữ liệu từ file được xác định bởi con trỏ FILE *fp. size : Kích cỡ tính theo bytes của mỗi phần tử được đọc, num: Số lượng các phần tử, mỗi phần tử có kích cỡ tương ứng với số bytes., buffer con trỏ trỏ đến khối bộ nhớ đệm. Hàm fread sẽ trả về số lượng các đối tượng có thể đọc. Nếu giá trị trả về 0 có nghĩa là không có đối tượng nào được đọc hoặc là kết thúc file hoặc là có lỗi gì đó trong quá trình đọc file. Có thể sử dụng feof() hoặc ferror() để xác định kết thúc file hoặc lỗi xảy ra trong quá trình đọc file.

int feof(FILE *fp);

int ferror(FILE *fp);

Hàm feof  trả về 0 khi vị trí đọc file (file đó được gắn kết với con trỏ *fp) đến điểm đánh dấu kết thúc file. Ngược lại sẽ trả về giá trị khác 0. Hàm ferror trả về giá trị khác 0 nếu quá trình đọc file (file đó được gắn kết với con trỏ *fp) xuất hiện lỗi. Và ngược lại nó sẽ trả về 0.

Hàm fwrite trái ngược với hàm fread, nó thực hiện ghi dữ liệu từ bộ nhớ vào file. Các tham số hình thức truyền vào cho hàm fwrite cũng tương tự như fread. Giá trị trả về cho hàm fwrite là số lượng các đối tượng được ghi từ bộ nhớ vào file.

Ví dụ

Sử dụng hàm fread và fwrite để đọc và nghi dữ liệu từ mảng nguyên chẵn với 10 phần tử.

Code minh họa


Hình số 1: Hàm fread và hàm fwrite

3. Truy cập file tuần tự và ngẫu nhiên

Khi thực hiện thao tác mở file để thực hiện việc đọc và ghi dữ liệu thì vị trí con trỏ đọc và ghi file là rất quan trọng (vị trí này được gọi là vị trí xác định (Con trỏ file)). Vị trí luôn được tính toán bằng bytes tính từ vị trí đầu của file. Khi một file mới được tạo và mở, thì vị trí xác định (Con trỏ file) luôn là vị trí đầu tiên của file có nghĩa là nó có giá trị 0. Bởi vì khi file được tạo mới thì file chưa có dữ liệu, và vì vậy không có một vị trí xác định (Con trỏ file) nào khác trên file ngoài vị trí đầu tiên là 0. Khi mở một file đã tồn tại, vị trí xác định là vị trí cuối cùng của file nếu file đó được mở ra để thực hiện chèn dữ liệu. Với các kiểu xử lý khác thì vị trí xác định luôn là vị trí đầu file.

Với các ví dụ ở phần đầu của bài viết khi thao tác với hàm vào/ra (fread và fwrite), vị trí đọc và ghi dữ liệu phụ thuộc vào việc tính toán tương ứng với các vòng lặp. Đầu tiên vị trí xác định là 0. Các vị trí xác định sẽ luôn được cập nhật tại mỗi bước lặp khác nhau. Do vậy nếu lập trình viên muốn đọc/ghi dữ liệu theo kiểu tuần tự (từ vị trí đầu đến vị trí đánh dấu của kết thúc file) thì cũng không cần quan tâm đến vị trí xác định (vị trí con trỏ đọc/ghi file – Con trỏ file) thực sự.

Trong trường hợp cần điểu khiển quá trình đọc/ghi file theo từng vị trí, khi đó việc xác định vị trí của con trỏ đọc/ghi sẽ cần phải thực hiện. Việc điều khiển vị trí xác định (Con trỏ file) này có thể được xử lý theo cơ chế truy cập ngẫu nhiên của file. Ngẫu nhiên có nghĩa là dữ liệu đọc/ghi được thực hiện ở bất kỳ vị trí nào trong file mà không cần phải đọc toàn bộ dữ liệu trong file. Vấn đền này sẽ được đề cập tại phần truy cập ngẫu nhiên của file.

4. Tệp của bản ghi – Tệp dữ liệu struct (cấu trúc).

Trong C sử dụng từ khóa struct để định nghĩa kiểu dữ liệu có cấu trúc. Các bản nghi được ghi vào đĩa một cách tuần tự. Tệp dữ liệu chứa các phần tử là kiểu struct cũng thuộc kiểu dữ liệu nhị phân.

4.1. Làm việc với tệp của bản ghi

Sử dụng fscanf() và fprintf() để làm việc với các tệp dữ liệu struct. Hàm fscanf được sử dụng đọc dữ liệu đầu vào từ luồng dữ liệu theo định dạng. Hàm fprintf gửi toàn bộ các output đã được định dạng tới một Stream.

Nguyên mẫu hàm fscanf

int fscanf(FILE *stream, const char *format-string, argment-list)

Trong đó:

*stream : Con trỏ trỏ đến file

*format: Hằng con trỏ kiểu char, chứa xâu ký tự nhằm mục tiêu xác định kiểu định dạng dữ liệu khi tiến hành đọc.

NguyênHàm fprintf

int fprintf(FILE *stream, const char *format, ...)

Trong đó:

*stream : Con trỏ trỏ đến file

*format: Hằng con trỏ kiểu char, chứa xâu ký tự nhằm mục tiêu xác định kiểu định dạng dữ liệu.

Ví dụ:

Định nghĩa một struct với các thành phần (itemcode, name, price). Thực hiện các thao tác Lưu thông tin dạng record vào item.dat, hiển thị thông tin, xóa thông tin, chỉnh sửa thông tin.

Code Minh họa

include <stdio.h>

struct item

{

  int itemcode;

  char name[30];

  double price;

};

void append();

void modify();

void dispall();

void dele();

 

int main()

{

  int ch;

  struct item it;

  FILE *fp;

  fp=fopen("item.dat",“w”);

  if(fp==NULL){

          printf("\n ERROR IN OPENING FILE...");

          exit(0);

  }

  printf("\n ENTER ITEM CODE:");

  scanf("%d",&it.itemcode);

  printf("\n ENTER ITEM NAME:");

  fflush(stdin);

  scanf("%[^\n]",it.name);

  printf("\n ENTER PRICE:");

  scanf(“%lf”,&it.price);

  fprintf(fp,"%d \t%s\t%lf\n",it.itemcode,it.name,it.price);

  fprintf(fp,"%d",0);

  fclose(fp);

  while(1){

          printf("\n \t 1.APPEND RECORD");

          printf("\n \t 2.DISPLAY ALL RECORD");

          printf("\n \t 3.EDIT RECORD");

          printf("\n \t 4.DELETE RECORD");

          printf("\n \t 5.EXIT");

          printf("\n \t ENTER UR CHOICE:");

          scanf(“%d”,&ch);

          switch(ch)

    {

                    case 1:append();    

                    case 2:dispall();    

                    case 3:modify();    

                    case 4:dele();    

                    case 5:exit(0);

    }

  }

  return 0;

}

void append()

{

  FILE *fp;

  struct item it;

  fp=fopen("item.dat","a");

  if(fp==NULL)

  {

           printf("\n ERROR IN OPENING FILE...");

            exit(0);

  }

  printf("\n ENTER ITEM CODE:");

  scanf("%d",&it.itemcode);

  printf(“\n ENTER ITEM NAME:”);

  fflush(stdin);

  scanf("%[^\n]",it.name);

  printf("\n ENTER PRICE:");

  scanf("%lf",&it.price);

  fprintf(fp,"%d \t%s\t%lf\n",it.itemcode,it.name,it.price);

  fprintf(fp,"%d",0);

  fclose(fp);

}

void dispall()

{

  FILE *fp;

  struct item it;

  fp=fopen("item.dat","r");

  if(fp==NULL)

  {

  printf("\n ERROR IN OPENING FILE...");

  exit(0);

  }

  while(1)

  {

  fscanf(fp, "%d",&it.itemcode);

  if(it.itemcode==0)

    break;

  fscanf(fp,"%s",it.name);

  fscanf(fp,"%lf",&it.price);

  printf("\n \t %d\t%s\t%lf",it.itemcode,it.name,it.price);

  }

  fclose(fp);

}

void modify()

{

  FILE *fp,*fptr;

  struct item it;

  int icd,found=0;

  fp=fopen("item.dat","r");

  if(fp==NULL)

  {

  printf("\n ERROR IN OPENING FILE...");

  exit(0);

  }

  fptr=fopen("temp.dat","w");

  if(fptr==NULL)

  {

          printf(“\n ERROR IN OPENING FILE...”);

          exit(0);

  }

  printf(“\n ENTER THE ITEM CODE TO EDIT”);

  scanf(“%d”,&icd);

  while(1)

  {

            fscanf(fp,"%d",&it.itemcode);

  if(it.itemcode==0)

    break;

  if(it.itemcode==icd)

  {

    found=1;

    fscanf(fp,"%s",it.name);

    fscanf(fp,"%lf",&it.price);

    printf("\n EXISTING RECORD IS...\n");

    printf("\n \t %d\t%s\t%lf",it.itemcode,it.name,it.price);

    printf("\n ENTER NEW ITEM NAME:");

    fflush(stdin);

    scanf("%[^\n]",it.name);

    printf("\n ENTER NEW PRICE:");

    scanf("%lf",&it.price);

    fprintf(fptr,"%d \t%s\t%lf\n",it.itemcode,it.name,it.price);

  }

  else

  {

    fscanf(fp,"%s",it.name);

          fscanf(fp,"%lf",&it.price);

    fprintf(fptr,"%d \t%s\t%lf\n",it.itemcode,it.name,it.price);

  }

 }

 fprintf(fptr,"%d",0);

 fclose(fptr);

 fclose(fp);

 if(found==0)

  printf("\nRECORD NOT FOUND...");

 else

 {

  fp=fopen("item.dat","w");

  if(fp==NULL)

  {

    printf(“\n ERROR IN OPENING FILE...”);

    exit(0);

  }

  fptr=fopen("temp.dat","r");

  if(fptr==NULL)

  {

          printf("\n ERROR IN OPENING FILE...");

    exit(0);

  }

  while(1)

  {

          fscanf(fptr,"%d",&it.itemcode);

          if(it.itemcode==0)

          break;

  fscanf(fptr,"%s",it.name);

  fscanf(fptr,"%lf",&it.price);

  fprintf(fp,"%d \t%s\t%lf\n",it.itemcode,it.name,it.price);

  }

  fprintf(fp,“%d”,0);

  fclose(fptr);

  fclose(fp);

 }

}

void dele()

{

  FILE *fp,*fptr;

  struct item it;

  int icd,found=0;

  fp=fopen("item.dat","r");

  if(fp==NULL)

  {

  printf("\n ERROR IN OPENING FILE...");

  exit(0);

  }

  fptr=fopen("temp.dat","w");

  if(fptr==NULL)

  {

  printf("\n ERROR IN OPENING FILE...");

  exit(0);

  }

  printf("\n ENTER THE ITEM CODE TO DELETE");

  scanf("%d",&icd);

  while(1)

  {

  fscanf(fp,"%d",&it.itemcode);

  if(it.itemcode==0)

    break;

  if(it.itemcode==icd)

  {

    found=1;

    fscanf(fp,"%s",it.name);

    fscanf(fp,“%lf”,&it.price);

  }

  else

  {

    fscanf(fp,"%s",it.name);

    fscanf(fp,"%lf",&it.price);

    fprintf(fptr,"%d \t%s\t%lf\n",it.itemcode,it.name,it.price);

  }

  }

  fprintf(fptr,"%d",0);

  fclose(fptr);

  fclose(fp);

  if(found==0)

  printf("\n RECORD NOT FOUND...");

  else

  {

  fp=fopen("item.dat","w");

  if(fp==NULL)

  {

    printf("\n ERROR IN OPENING FILE...");

    exit(0);

  }

  fptr=fopen("temp.dat","r");

  if(fptr==NULL)

  {

    printf("\n ERROR IN OPENING FILE...");

    exit(0);

  }

  while(1)

  {

    fscanf(fptr,"%d",&it.itemcode);

    if(it.itemcode==0)

    break;

    fscanf(fptr,"%s",it.name);

    fscanf(fptr,"%lf",&it.price);

    fprintf(fp, "%d \t%s\t%lf\n",it.itemcode,it.name,it.price);

  }

  fprintf(fp,"%d",0);

  fclose(fptr);

  fclose(fp);

 }

}

Trong ví dụ trên, có áp dụng việc tạo menu dạng đơn giản trong chương trình C. Mỗi chức năng sẽ được chia nhỏ ra thành các hàm append(); modify();dispall(); dele();

5. Truy cập trực tiếp vào tệp dữ liệu cấu trúc (struct)

Để truy cập trực tiếp vào dữ liệu tệp struct, C cung cấp các hàm :

+ fseek
+ ftell

+ rewind

Sử dụng hàm fseek()  thiết lập trị ví con trỏ file (vị trị trí xác định) (Vị trí con trỏ hiện thời trong file). Hàm này trong stdio.h. 

Nguyên mẫu của hàm

int fseek(FILE *fp, long offset, int origin);

Trong đó :

*fp: Con trỏ kiểu FILE

offset: Chỉ số lượng byte khi thực hiện di chuyển từ vị trí xác định (Con trỏ file) (Đây là số byte để offset từ đó). Thực tế giá trị của nó được tính bằng:

offset = no*Kích_thước_1_phần_tử

trong đó no là vị trí thứ tự của phần tử trong tệp. Lưu ý phần tử đầu tiên của tệp sẽ có giá trị 0.

Kích_thước_1_phần_tử : Thường được xác định bởi hàm sizeof().

Origin: Đây là vị trí từ đó offset được thêm vào. Nó được xác định bởi một trong số các hằng sau:


Bản số 1: Các giá trị tham số origin

Ví dụ :

Sử dụng hàm fseek để xác định vị trí và để thực hiện đọc giá trị trong file data.dat trong ví dụ trên.

Code minh họa

#include <stdio.h>

#include <string.h>

struct item{

  int itemcode;

  char name[30];

  double price;

};

typedef struct item product;

FILE *fp;

int main()

  {

  product it;

  int rec, result;

  fp = fopen("item.dat", "r+b");

  printf("Which record do you want [0-3]? Press\-1 to exit...");

  scanf(“%d”, &rec);

  while(rec >= 0)

  {

  fseek(fp, rec*sizeof(it), SEEK_SET);

  result = fread(&it, sizeof(it), 1, fp);

  if(result==1)

  {

    printf("\nRECORD %d\n", rec);

    printf("Item code........: %d\n",it.itemcode);

    printf("Item name.......: %s\n", it.name);

    printf("Price...: %8.2f\n\n", it.price);

  }

  else

  printf("\nRecord %d not found!\n\n", rec);

  printf("Which record do you want [0-3]? Press -1 to exit...");

  scanf("%d", &rec);

  }

  fclose(fp);

  return 0;

}

Khi thực hiện đọc nội dung của file bằng cách truy cập ngẫu nhiên theo từng vị trí. Để di chuyển vị trí xác định (Con trỏ file hay con trỏ đọc file) thì chúng ta sử dụng hàm :

void rewind(FILE *fp);

Khi gọi hàm rewind vị trí xác định sẽ == 0;

Để xác định chính xác vị trí hiện tại của con con trỏ file. Chúng ta sử dụng hàm ftell(). Hàm này có nguyên mẫu như sau:

long ftell(FILE *fp);

Trong trường hợp bị lỗi ,hàm trả về -1.

6. Các hàm quản lý khác của File

6.1. Xóa file

C sử dụng hàm remove() để thực hiện xóa file.

Nguyên mẫu hàm

int remove(const char *filename);

*filename: Hằng con trỏ kiểu char, trỏ đến tên file.

Hàm trả về 0 : Nếu filename tồn tại và file cho phép xóa file, và quá trình xóa file thành công.

Ngược lại : Hàm sẽ trả về giá trị là -1.

Ví dụ:

#include <stdio.h>

int main(void)

{

 char file[80];

 /* prompt for filename to delete */

 printf("File to delete: ");

 gets(file);

 /* delete the fi le */

 if(remove(file) == 0)

  printf("Removed %s.\n",fi le);

 else

  perror("remove");

  return 0;

}

Trong ví dụ trên, sử dụng hàm perror() để hiển thị thông báo lỗi.

Nguyên mẫu hàm

void perror(const char *message);

Trong đó : *message chứa xâu ký tự hiển thị thông báo khi lỗi.

6.2. Đổi tên file

Sử dụng hàm rename() để thực hiện đổi tên file.

Nguyên mẫu của hàm:

int rename(const char *oldname, const char *newname);

trong đó:

*oldname: Con trỏ kiểu char, trỏ tới tên file cũ

* newname: Con trỏ kiểu char, Tên file mới

Hàm trả về 0 nếu thành công, ngược lại trả về giá trị -1;

Ví dụ:

#include <stdio.h>

int main(void)

{

 char oldname[80], newname[80];

 /* prompt for fi le to rename and new name */

 printf("File to rename:");

 gets(oldname);

 printf("New name:");

 gets(newname);

 /* Rename the fi le */

 if(rename(oldname, newname) == 0)

  printf("Renamed %s to %s.\n", oldname, newname);

 else

  perror("rename");

  return 0;

}

Hàm thực hiện đổi tên file, tên file cũ và file mới được nhập bằng bàn phím. Sử dụng hàm rename để thực hiện đổi tên và if để kiểm tra quá trình đổi tên có thành công hay không.


vertical_align_top
share
Chat...