1、随机读写文件
前面介绍的文件读写函数都是顺序读写,即读写文件只能从头开始,依次读写各个数据。但在实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写。这种读写方式称为随机读写,也就是说从文件的任意位置开始读写。
实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。
文件定位函数 rewind 和 fseek
移动文件内部位置指针的函数主要有两个,即 rewind() 和 fseek()。
rewind() 用来将位置指针移动到文件开头,前面已经多次使用过,它的原型为:
void rewind ( FILE *fp );
fseek() 用来将位置指针移动到任意位置,它的原型为:
int fseek ( FILE *fp, long offset, int origin );
参数说明:
fp 为文件指针,也就是被移动的文件。
offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。offset 为正时,向后移动;offset 为负时,向前移动。
origin 为起始位置,也就是从何处开始计算偏移量。C 语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾,每个位置都用对应的常量来表示:
例如,把位置指针移动到离文件开头 100 个字节处:
fseek(fp, 100, 0);
值得说明的是,fseek() 一般用于二进制文件,在文本文件中由于要进行转换,计算的位置有时会出错。
文件的随机读写
在移动位置指针之后,就可以用前面介绍的任何一种读写函数进行读写了。由于是二进制文件,因此常用 fread() 和fwrite() 读写。
【示例】从键盘输入三组学生信息,保存到文件中,然后读取第二个学生的信息。
1. #include<stdio.h>
2.
3. #define N 3
4.
5. struct stu{
6. char name[10]; //姓名
7. int num; //学号
8. int age; //年龄
9. float score; //成绩
10. }boys[N], boy, *pboys;
11.
12. int main(){
13. FILE *fp;
14. int i;
15. pboys = boys;
16. if( (fp=fopen("d:\\\\demo.txt", "wb+")) == NULL ){
17. printf("Cannot open file, press any key to exit!\\n");
18. getch();
19. exit(1);
20. }
21.
22. printf("Input data:\\n");
23. for(i=0; i<N; i++,pboys++){
24. scanf("%s %d %d %f", pboys->name, &pboys->num, &pboys->age, &pboys->score);
25. }
26. fwrite(boys, sizeof(struct stu), N, fp); //写入三条学生信息
27. fseek(fp, sizeof(struct stu), SEEK_SET); //移动位置指针
28. fread(&boy, sizeof(struct stu), 1, fp); //读取一条学生信息
29. printf("%s %d %d %f\\n", boy.name, boy.num, boy.age, boy.score);
30.
31. fclose(fp);
32. return 0;
33. }
运行结果: Input data: Tom 2 15 90.5↙
Hua 1 14 99↙
Zhao 10 16 95.5↙
Hua 1 14 99.000000
2、实现文件复制功能
文件的复制是常用的功能,要求写一段代码,让用户输入要复制的文件以及新建的文件,然后对文件进行复制。能够复制的文件包括文本文件和二进制文件,你可以复制 1G 的电影,也可以复制 1Byte 的 txt 文档。
实现文件复制的主要思路是:开辟一个缓冲区,不断从原文件中读取内容到缓冲区,每读取完一次就将缓冲区中的内容写入到新建的文件,直到把原文件的内容读取完
这里有两个关键的问题需要解决:
开辟多大的缓冲区合适?缓冲区过小会造成读写次数的增加,过大也不能明显提高效率。目前大部分磁盘的扇区都是 4K 对齐的,如果读写的数据不是 4K 的整数倍,就会跨扇区读取,降低效率,所以我们开辟 4K 的缓冲区。
缓冲区中的数据是没有结束标志的,如果缓冲区填充不满,如何确定写入的字节数?最好的办法就是每次读取都能返回读取到的字节数。
fread() 的原型为:
size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
它返回成功读写的块数,该值小于等于 count。如果我们让参数 size 等于 1,那么返回的就是读取的字节数。
注意:fopen()一定要以二进制的形式打开文件,不能以文本形式打开,否则系统会对文件进行一些处理,如果是文本文件,像.txt 等,可能没有问题,但如果是其他格式的文件,像.mp4, .rmvb, .jpg 等,复制后就会出错,无法读取。
代码实现:
1. #include <stdio.h>
2. #include <stdlib.h>
3.
4. int copyFile(char *fileRead, char *fileWrite);
5.
6. int main(){
7. char fileRead[100]; // 要复制的文件名
8. char fileWrite[100]; // 复制后的文件名
9.
10. // 获取用户输入
11. printf("要复制的文件:");
12. scanf("%s", fileRead);
13. printf("将文件复制到:");
14. scanf("%s", fileWrite);
15.
16. // 进行复制操作
17. if( copyFile(fileRead, fileWrite) ){
18. printf("恭喜你,文件复制成功!\\n");
19. }else{
20. printf("文件复制失败!\\n");
21. }
22.
23. return 0;
24. }
25.
26. /**
27. * 文件复制函数
28. * @param fileRead 要复制的文件
29. * @param fileWrite 复制后文件的保存路径
30. * @return int 1: 复制成功;2: 复制失败
31. **/
32. int copyFile(char *fileRead, char *fileWrite){
33. FILE *fpRead; // 指向要复制的文件
34. FILE *fpWrite; // 指向复制后的文件
35. int bufferLen = 1024*4; // 缓冲区长度
36. char *buffer = (char*)malloc(bufferLen); // 开辟缓存
37. int readCount; // 实际读取的字节数
38.
39. if( (fpRead=fopen(fileRead, "rb")) == NULL || (fpWrite=fopen(fileWrite, "wb")) == NULL ){
40. printf("Cannot open file, press any key to exit!\\n");
41. getch();
42. exit(1);
43. }
44.
45. // 不断从 fileRead 读取内容,放在缓冲区,再将缓冲区的内容写入 fileWrite
46. while( (readCount=fread(buffer, 1, bufferLen, fpRead)) > 0 ){
47. fwrite(buffer, readCount, 1, fpWrite);
48. }
49.
50. free(buffer);
51. fclose(fpRead);
52. fclose(fpWrite);
53.
54. return 1;
55. }
运行结果:
要复制的文件:d://1.mp4
将文件复制到:d://2.mp4
恭喜你,文件复制成功!
如果文件不存在,会给出提示,并终止程序:
要复制的文件:d://123.mp4
将文件复制到:d://333.mp4
d://cyuyan.txt: No such file or directory
第 46 行是文件复制的核心代码。通过 fread()函数,每次从 fileRead 文件中读取 bufferLen 个字节,放到缓冲区,再通过 fwrite()函数将缓冲区的内容写入 fileWrite 文件。
正常情况下,每次会读取 bufferLen 个字节,即 readCount=bufferLen;如果文件大小不足 bufferLen 个字节,或者读取到文件末尾,实际读取到的字节就会小于 bufferLen,即 readCount<bufferLen。所以通过 fwrite()写入文件时,应该以 readCount 为准。