Xem Nhiều 4/2024 # Đào Tạo Lập Trình Viên Chuyên Nghiệp # Top 1 Yêu Thích

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); file nguồn

  if(!feof(ofp))

          fputc(ch, nfp); 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 chúng tôi hiển

thị thông tin, xóa thông tin, chỉnh sửa thông tin.

Code Minh họa

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%st%lfn”,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 chúng tôi 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%st%lfn”,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 %dt%st%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 %dt%st%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%st%lfn”,it.itemcode,it.name,it.price);

  }

  else

  {

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

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

    fprintf(fptr,”%d t%st%lfn”,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%st%lfn”,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%st%lfn”,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%st%lfn”,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 chúng tôi trong ví dụ trên.

Code minh họa

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 %dn”, rec);

    printf(“Item code……..: %dn”,it.itemcode);

    printf(“Item name…….: %sn”, it.name);

    printf(“Price…: %8.2fnn”, it.price);

  }

  else

  printf(“nRecord %d not found!nn”, 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ụ:

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ụ:

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.