文件I/O操作
文件描述符:
在Linux中,每打开一个磁盘文件,都会在内核中建立一个文件表项,文件表项中储存着文件的状态信息,储存文件内容的缓冲区和当前文件的读写位置。打开几次就会创建几个这样的文件表项,读写该文件,只会修改该文件表项的文件读写位置。这几个文件表项储存在一个文件表数组tablle[]中。这个文件表的下表就被称为文件描述符。通过文件描述符就可以访问到这个磁盘。
数据流:
从数据操作方式来说,Linux中的文件都可以看做数据流。在对文件进行操作前,必须调用f标准I/O库函数fopen()将数据流打开,打开后才可以操作。
标准I/O库函数是C语言中所持有的用于高级接口的函数,存在stdio.h头文件中,这些用于数据流的I/O操作函数还适用于其他系统,移植性好。
要对数据流近读写操作,需要标准I/O库函数和FILE类型的文件指针。指针是打开数据流时返回的指针,用指针来表示要操作的数据流。
有3中数据流不需要特定的函数打开操作,分别是标准输入、标准输出、标准错误输出。
对数据流操作完后,需要关闭,调用fclose()函数。该函数在关闭数据流之前,会 清空在操作过程中分配的缓冲区并保存数据信息。
基于文件描述符的I/O操作
文件的打开与关闭
open()函数
可以打开或者创建一个文件(包括设备文件)
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);
int creat(cconst char *pathname,mode_t mode);
open()函数和create()函数调用成功,会返回新分配的文件描述符;失败,返回-1,并设置适当的errno值。
pathname:文件的路径名称
flags:文件的打开方式的宏定义
mode: 文件的访问权限设置
文件权限由open()函数的mode参数和当前进程的umask掩码共同决定,umask掩码是系统默认的值,可以在终端下输入umask命令查询。
close()函数
用于关闭一个已打开的文件
#include<unistd.h>
int close(int fd);
调用成功,返回0,失败,返回-1,并设置适当的errno值。
fd:要关闭的文件描述符
文件的读写操作
read()函数
用于从打开的文件(包括设备文件)中读取数据
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t count);
fd:要读写的文件的文件描述符
buf:读取的数据存放在buf指针所指的缓冲区
count: 读取的数据的字节数
读取文件数据时,文件的当前读写位置会往后移。这个读写位置与C标准I/O库的读写位置而可能不同,这边的是记在内核中,二C标准I/O库时的读写位置时用户空间I/O缓冲区中的位置。
调用成功,返回读取的字节数;失败返回-1,并设置适当的errno值。
返回字节数可能会小于参数count值。
write()函数
用于向打开的设备或文件中写入数据
#include<unistd.h>
ssize_t write(int fd,const void *buf,size_t count);
fd:要写入数据的文件的文件描述符
buf:写入数据在buf指针所指的缓冲区
count:写入的数据的字节数
调用成功,返回写入的字节数;失败,返回-1,并设置适当的errno值。
当向常规文件吸入数据,返回时count字节数;当向终端设备或者网络写入数据,返回值不一定是写入的字节数。
文件的定位
lseek()函数
移动当前读写位置
#include<sys/types.h>
#include<unistd.h>
off_t lseek(int fildes,off_t offset,int whence);
fildes:文件描述符
offset:偏移量
whence:代表用于偏移时的相对位置
whence取值:
- SEEK_SET:从文件的开头位置计算偏移量
- SEEK_CUR:从当前的位置计算偏移量
- SEEK_END:从文件的末尾计算偏移量
偏移量可以超过文件末尾,对文件进行延迟()使用"\0"填充
调用成功返回新的偏移量;失败返回-1,并设置适当的errno值
例:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
char *path="test.c"; /*进行操作的文件路径*/
int fd;
char buf[40],buf2[]="hello mrcff"; /*自定义读写用的缓冲区*/
int n,i;
if((fd=open(path,O_RDWR))<0) /*打开文件*/
{
perror("open file failed!");
return 1;
}
else
printf("open file successful!\n");
if((n=read(fd,buf,20))<0) /*读取文件中的数据*/
{
perror("read failed!");
return 1;
}
else
{ printf("output read data:\n");
printf("%s\n",buf); /*将读取的数据输出到终端控制台*/
}
if((i=lseek(fd,11,SEEK_SET))<0) /*定位到从文件开头处到第11个字节处*/
{
perror("lseek error!");
return 1;
}
else
{
if(write(fd,buf2,11)<0) /*向文件中写入数据*/
{
perror("write error!");
return 1;
}
else
{
printf("write successful!\n");
}
}
close(fd); /*关闭文件的同时保存了对文件的改动*/
if((fd=open(path,O_RDWR))<0) /*打开文件*/
{
perror("open file failed!");
return 1;
}
if((n=read(fd,buf,40))<0) /*读取数据*/
{
perror("read 2 failed!");
return 1;
}
else
{
printf("read the changed data:\n");
printf("%s\n",buf); /*将数据输出到终端*/
}
if(close(fd)<0) /*关闭文件*/
{
perror("close failed!");
return 1;
}
else
printf("good bye!\n");
return 0;
}
基于数据流的I/O操作
基于数据流的I/O操作是通过一个FILE类型的文件指针实现对文件的访问的。在FILE结构体类型中储存着许多关于流操作所需要的信息。这些函数都是存放在stdio.h头文件中声明的。
文件的打开与关闭
fopen()函数
#cinclude<stdio.h>
FILE *fopen(const char *path,const char *mode);
path:打开的文件的路径名
mode: 文件的打开方式
调用成功,返回文件指针;失败返回NULL,并设置适当的errno值。
mode参数不介绍了,具体百度,自己搜。
fclose()函数
#include<stdio.h>
int fclose(FILE *fp);
fp:要关闭的文件的指针。
调用成功返回-;失败返回EOF(-1)并设置适当的errno值。
字符输入/输出
fgetc()函数
#include<stdio.h>
int fgetc(FILE *stream);
参数略
从指定文件中读取一个字节
调用成功,返回读到的字节;失败或这读到文件末尾,返回EOF(-1)
fputc()函数
#include<stdio.h>
int foutc(int c,FILE *stream);
参数略
把字符c写入stream指针所指的文件中。
调用成功返回写入的字节,否则返回EOF(-1)。
例:
多次调用fputc()函数向文件test.c中写入数组a中的戒子,然后通过多次调用fgetc()函数获取文件中的数据存放在字符变量ch中,最终显示到终端屏幕上。
#include<stdio.h>
int main()
{
FILE *fp;
int i;
char *path="test.c";
char a[]={'h','e','l','l','o',' ','m','r'};
char ch;
fp=fopen(path,"w");/*以只写打开文件*/
if(fp) /*判断是否成功打开文件*/
{
for(i=0;i<5;i++)
{
if(fputc(a[i],fp)==EOF)/*向文件中循环写入a数组中的内容*/
{
perror("write error!");
return 1;
}
}
printf("write successful!\n");
}
else
{
printf("open error!\n");
return 1;
}
fclose(fp); /*关闭文件*/
if((fp=fopen("test.c","r"))==NULL)/*以只读的形式打开文件*/
{
perror("open error!");
return 1;
}
printf("output data in the test.c\n");
for(i=0;i<5;i++)
{
if((ch=fgetc(fp))==EOF)/*循环方式获取文件中的5个字节*/
{
perror("fgetc error!");
return 1;
}
else
{
printf("%c",ch);/*输出字符*/
}
}
printf("\nget successful!\nplease examine test.c...\n");
fclose(fp);/*关闭文件*/
return 0;
}
字符串输入/输出
fgets()函数
#include<stdio.h>
char *fgets(char *s,int size,FILE *stream);
实现了从参数stream所指的文件中读取一串小于参数size所代表字节数的字符串,并将字符串储存到s所指的缓冲区中。
函数调用成功,返回内容为s所指的缓冲区部分的指针;失败(的渠道文件末尾或者出错)返回NULL。
在size字节范围内没有读到“\n”结束符,增加一个"\0"组成字符串储存到缓冲区中;文件剩余的字符在下次调用fgets()函数再读取。
fgets()函数只用于读取文本文件,不推荐读二进制("\n"在函数中为结束符,"\0"是普通字符,无法判断这个是自动增加的还是文件读出来的)
fputs()函数
#include<stdio.h>
int fputs(const char *s,FILE *stream);
向stram指针所指的文件中写入s缓冲区的字符串。
调用成功,返回一个非负整数;失败,返回EOF。(-1)
数据块输入/输出
fread()函数和fwrite()函数
#include<stdio.h>
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
size_t fwrite(const void *ptr,sizt_t size,size_t nmemb,FILE *stream);
size:一个数据块大小
nmemb:要读或些多少条这样的数据块
stream:文件指针
ptr:指针所指的内存空间
调用成功,返回读或写的记录,记录数等于nmemb;失败(出错或读到文件末尾),可能返回0
格式化输入/输出
格式化输出函数
#include<stdio.h>
int printf(const char *format,...);
int fprintf(FILE *stream,const char *format,...);
int sprintf(char *str,const char *format,...);
int snprintf(char *str,size_t size,const char *format,...);
调用成功,返回格式化输出的字节数(不包括字符串结尾"\0");失败,返回一个负值。
printf()略
fprintf()函数是向文件中输出字符串
snprintf()和sprintf()函数是向str所代表的字符串中输出数据,size为缓冲区的大小(防止溢出)
格式化输入函数
#include<stdio.h>
int scanf(const char *format,...);
int fscanf(FILE *stream,const char *format,...);
int sscanf(const char *str,const char *format,...);
fscanf()函数从指定的文件获取字符
sscanf()函数从哪个指定而字符串str中获取字符
调用成功,返回成功匹配和赋值的参数的个数,成功匹配的参数可能少提供给的赋值参数。返回0表示一个都不匹配;失败,返回EOF,并设置errno值。
操作读写位置的函数
fseek()函数
#include<stdio.h>
int fseek(FILE *stream,long offset,int whence);
一般用于二进制文件。
whence表四从何处开始计算位移量。SEEK_SET(0,文件首),SEEK_CUR(1,文件当前位置),SEEK_END(2,文件结尾)
调用成功,返回0;失败返回-1,并设置适当的errno值。
例:
在一个文件的第二个字节处插入字符m
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp;
if ( (fp = fopen("file.c","r+")) == NULL) /*以读写的方式打开一个已存在的文件file.c*/
{
perror("Open file textfile");
exit(1);
}
if (fseek(fp, 2, SEEK_SET) != 0)/*将读写位置定位在从文件开头处计算的第2个字节处*/
{
perror("Seek file textfile");
exit(1);
}
fputc('m', fp); /*在此处插入字符m*/
fclose(fp); /*关闭文件*/
return 0;
}
ftell()函数
#include<stdio.h>
long ftell(FILE *stream);
得到stream指定的流文件中的当前位置。
调用成功,返回当前的位移量;失败返回-1并设置适当的errno值
rewind()函数
#include<stdio.h>
void rewind(FILE *stream);
使位置指针重新返回文件的开头
例:
#include<stdio.h>
#include<stdlib.h>
main()
{
FILE *fp;
char ch,filename[50];
printf("请输入文件路径及名称:\n");
scanf("%s",filename); /*输入文件名*/
if((fp=fopen(filename,"r"))==NULL) /*以只读方式打开该文件*/
{
printf("不能打开的文件!\n");
exit(0);
}
printf("len0=%d\n",ftell(fp)); /*输出当前位置*/
ch = fgetc(fp);
while (ch != EOF)
{
putchar(ch); /*输出字符*/
ch = fgetc(fp); /*获取fp指向文件中的字符*/
}
printf("\n");
printf("len1=%d\n",ftell(fp)); /*输出位置指针的当前位置*/
rewind(fp); /*指针指向文件开头*/
printf("len2=%d\n",ftell(fp)); /*输出位置指针当前位置*/
ch = fgetc(fp);
while (ch != EOF)
{
putchar(ch); /*输出字符*/
ch = fgetc(fp);
}
printf("\n");
fclose(fp); /*关闭文件*/
}
C标准库的I/O缓冲区
C标准库调用fopen()函数都会给文件分配一个I/O缓冲区,来加速读写操作。
为文件分配内存缓冲区的大小,直接影响到实际操作外村设备的数量,配的越大,操作外存的数量越少,读写速度越快,效率提高。
缓冲区分为三种
全缓冲区:
缓冲区写满了,就写回内核。普通文件通常是全缓冲的。
行缓冲区:
数据中带有"\n"就把张写回内核。标准输入和标准输出对应终端设备时通常是行缓存的。
无缓冲:
标准错误输出通常是无缓冲的。
设置缓冲区属性
#include<stdio.h>
void setbuf(FILE *stream,char *buf);
void setbuffer(FILE *stream,char *buf,size_t size);
void setlinebuf(FILE *stream);
int setvbuf(FILE *stream,char *buf,int mode,size_t size);
setbuf()函数设置buf所指的缓冲区大小。缓冲区大小有两个,一个是BUFSIZ(全缓冲区),一个是NULL(无缓冲)。
setbuffer()和setbuf()函数相同,但可以指定缓冲区大小。
setlinebuf()函数设置为行缓冲。
setvbuf()函数融合了上面3种函数的功能。mode的取值_IOFBF(全缓冲)、_IOLBT(行缓冲)、_IONBF(无缓冲)
调用成功返回0;失败返回非0值。
清空缓冲区
flush()将I/O缓冲区数据强制保存到磁盘文件中。
fflush()函数
#include<stdio.h>
int fflush(FILE *stream);
将缓冲区中尚未写入文件的数据强制性写入文件,然后清空缓冲区。
如果stream为NULL,则会将所有打开的文件数据更新。