19:文件
文件:是一组相关数据的有序结合
19.1:文件概念
文件分类:
- 普通文件:存储在磁盘等外部设备中,在需要的时候由程序将数据读入内存
- 设备文件:外部设备对于操作系统来说也是当做一个文件进行管理,把它们的输入、输出等同于对磁盘文件的读和写。比如显示器、打印机、键盘等。
- 文件夹:包含普通文件和子文件夹
文件系统:操作系统中用于组织和管理磁盘上文件的方法以及数据结构。
文件系统作用:为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。程序中文件操作,之后都由文件系统去替程序完成。
文件系统有:
- Windows: FAT,NTFS
- Linux:Ext2,Ext3,Ext4
- Solaris:ZFS(Linux一直想移植,但由于协议冲突等等一直没有实现)
- 其他等
如上图文件系统可以理解为三层:
- 最上层文件系统的系统调用
- 虚拟文件系统 VFS(Virtual Filesystem Switch)
- 挂载到VFS中的各实际文件系统
ZFS是一款128bit文件系统,总容量是现有64bit文件系统的1.84x1019倍,其支持的单个存储卷容量达到16EiB(264byte,即 16x1024x1024TB);一个zpool存储池可以拥有264个卷,总容量最大256ZiB(278byte);整个系统又可以拥有2^64个存储 池。可以说在相当长的未来时间内,ZFS几乎不太可能出现存储空间不足的问题。另外,它还拥有自优化,自动校验数据完整性,存储池/卷系统易管理等诸多优点。较ext3系统有较大运行速率,提高大约30%-40%。
文本文件与二进制文件:
- 文本文件:基于字符编码,以固定长度的二进制序列进行编码和解码(常见的有ASCII文编码和Unicode编码);(使用notepad就可以读取);(纯文本文件:.c文件.txt.html等)
- 二进制文件:基于值编码的文件,二进制文件编码是变长的,具体的长度由具体的格式决定,比如EXE或者BMP二进制文件;(需要专门的工具读取,比如图片就需要专门的读图软件才能打开,如果用notepad打开,就会看见不少乱码);(二进制文件:exe\dll\jpg\png\avi\doc\pdf等)
文本“5678”的存储形式为:字符的ASCII码: 00110101 00110110 00110111 00111000 (四个字节)
值5678的存储形式为:值的二进制: 00010110 00101110 (两个字节)
19.2:文件创建打开与读写
19.2.1:打开文件fopen_s
//文件读写:fopen(), fopen_s()
函数原型:FILE * fopen(const char * path, const char * mode);
返回值:文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回 NULL,并把错误代码存在errno中。
参数说明:
参数 path字符串包含欲打开的文件路径及文件名,参数 mode 字符串则代表着流形态。
一般而言,打开文件后会做一些文件读取或写入的动作,若打开文件失败,接下来的读写动作也无法顺利进行,所以一般在 fopen() 后作错误判断及处理。
//fopen("newfile.txt", "rw, ccs=UTF-8");//默认为ANSI
//fopen是C标准IO库的函数,与非C标准库函数open()函数相比,用fopen打开的文件读写是带缓存的,即用fwrite向文件里写一个字节,一般来讲它不会立刻调用write将该操作提交给kernel,而是积累到一定程度再一起写。
//fopen_s()函数是用于fopen()的新的安全版本。现在都推荐使用fopen_s()来打开文件。它的调用方法:
FILE *pfile=NULL;
errno_t err = fopen_s(&pfile,FILENAME,"wb+");
if(err!=0 || pfile==NULL) {
return -1;
}
fclose(pfile);完成文件IO后,不要忘调用fclose()函数将文件关闭。
fopen()函数.png
19.2.2:读写文件
在C语言里,可以使用fread/fwrite,fscanf/fprintf,fgets/fputs,fgetc/fputc等函数来进行文件的读写。
C语言中文件读写几种类型:
- 二进制文件读写
- 格式化输入输出到文本文件
- 字符输入输出到文本文件
- 字符串输入输出到文本文件
//二进制文件读写
int binary_io() {
//打开或者创建文件
char *path = "h:\\doc\\new.txt";
FILE *fp1 = NULL;
errno_t err;
err = fopen_s(&fp1,path,"w");
if(fp1==NULL || err != 0) {
printf("Open file failed\n");
return -1;
}
//基于fp1这个指针,对文件进行读写等操作
char buff[]="hello world";
fwrite(buff,sizeof(buff),1,fp1);
int data = 0x10;
fwrite(&data,sizeof(data),1,fp1);
fclose(fp1);
//读数据
err = fopen_s(&fp1,path,"r");
if(fp1==NULL || err!=0) {
return -1;
}
data = 0;
memset(buff,0,sizeof(buff));
fread(buff,sizeof(buff),1,fp1);
printf("buff:%s\n", buff);
fread(&data,sizeof(data),1,fp1);
printf("data:0x%x\n", data);
fclose(fp1);
return 0;
}
//格式化输入输出到文本文件(将除了ascci之外的数据以文本的方式写入文件)
int format_io() {
char *file="h:\\doc\\1.txt";
FILE *pfile=NULL;
errno_t err = fopen_s(&pfile,file,"w");
if(err!=0 || pfile==NULL){
printf("Open file failed\n");
return -1;
}
fprintf(pfile,"%s %x %lf","hello-world", 0x10,3.1415);
fclose(pfile);
char buff[64]={0};
int data=0;
double d = 0.0;
err=fopen_s(&pfile,file,"r");
if(err!=0) {
printf("Open file failed\n");
return -1;
}
fscanf_s(pfile,"%s%x%lf",buff,64,&data,&d);
printf("buff:%s,data:%d,d:%lf\n",buff,data,d);
fclose(pfile);
return 0;
}
//字符输入输出到文本文件
char *str="hello world, goodbye world!";
while(*str!='\0') {
fputc(*str,pfile);
str++;
}
//字符串输入输出到文本文件
19.3:文件其他相关操作
- fseek(被打开文件的指针, 移动的偏移-偏移值可以为正或者为负,代表第二个参数的偏移相对于什么位置):移动文件的当前读写位置指针。fseek(fp,50,SEEK_CUR);将文件的读写指针移动到当前位置后面50个字节。而SEEK_SET则是相对于文件开头,SEEK_END是文件结尾。fseek(fp, -50, SEEK_END);就是将文件读写指针移动到距离文件结束50个字节的位置。
- rewind():rewind(fp)执行函数后,会将文件fp当前的读写位置移动到文件的开头位置。等同于:fseek(fp,0,0);
- feof():用来判断文件当前位置是否达到文件末尾,如果到达,就返回真,否则为假。
- rename/remove/mkdir:文件的重命名,删除,创建文件夹都是与文件有关的一些基本操作。
- ftell():用于获取文件读写指针的当前位置。可以结合fseek()函数来计算文件的大小。
19.4:结构体IO与优化
//定义结构体:
#define MAXLEN 64
typedef struct _record {
char name[MAXLEN];
int age;
}record,*precord;
record r = {“tom”,25};
//按下方法将r写入文件,导致r成员name[MAXLEN]中大量零被写入文件中存放很多无用数据零。这样直接写入不是最佳方法:
fwrite(&r,sizeof(r),1,fp);
//考虑将结构体r按格式写入文件:
//假如name[MAXLEN]中的有效字符个数为len,那么写入方法为:
Len+name+age
//也就是先将name的字符数len写入文件,接着写入name的有效字符数据,再写入年龄数据。其中len和age各为4个字节的整数,name为字符。比如对于下面结构体记录数据:
record r1 = {“tom”,25};
record r2 = {“lily”,22};
//3”tom”25 4”lily”22;这样在每个name前面都用一个长度来记录name中字符的个数,就可以避免将过多的无用的零写入文件了。如下
int opt_write_file(FILE *fp) {
char ch;
do {
record rcd={0};
printf("Please input name and age\n");
scanf_s("%s%d", rcd.name,MAXLEN,&rcd.age);
size_t len = strlen(rcd.name);
fwrite(&len,sizeof(len),1,fp);//先写入name中字符的个数
fwrite(rcd.name,len,1,fp);//接着写入name字符数
fwrite(&rcd.age,sizeof(int),1,fp);//再写入年龄
printf("input q to quit,else to continue\n");
//scanf_s("%c",&ch,1);
//fflush(stdin);
ch = _getch();
if(ch=='q')
break;
} while (1);
return 0;
}
int opt_read_file(FILE *fp) {
while(!feof(fp)) {
record rcd = {0};
size_t len=0;
int res = fread(&len,sizeof(len),1,fp);//先读取name的字符数
if(res == 0) {
return 0;
}
fread(rcd.name,len,1,fp);//再按照字符数读取name
fread(&rcd.age,sizeof(rcd.age),1,fp);//再读取年龄
printf("name:%s,age:%d,res:%d\n", rcd.name,rcd.age,res);
}
return 0;
}
网友评论