文件IO与标准IO操作函数

1.定义

标准IO:标准I/O是ANSI C建立的一个标准I/O模型,是一个标 准函数包和stdio.h头中的定义,具有一定的可移植性。标准IO库处理很多细节。例如缓存分配,以优化长度执行IO等。标准的IO提供了三种类型的缓存。
(1)全缓存:当填满标准IO缓存后才进行实际的IO操作。
(2)行缓存:当输入或输出中遇到新行符时,标准IO库执行IO操作。
(3)不带缓存:只要用户调用该函数,就会写到内核中,stderr就是。

文件IO:文件IO称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定。

文件IO(不带缓存的I/O)对是文件描述符操作,标准IO(带缓存的I/O)是针对流操作的。

2.区别

首先:标准I/O默认采用了缓冲机制,比如调用fopen函数,不仅打开一个文件,而且建立了一个缓冲区(读写模式下将建立两个缓冲区),还创建了一个包含文件和缓冲区相关数据的数据结构(FILE *)。
低级I/O一般没有采用缓冲,需要自己创建缓冲区,不过其实在linux系统中,都是有使用称为内核缓冲的技术用于提高效率,读写调用是在内核缓冲区和进程缓冲区之间进行的数据复制。
使用标准IO就不需要自己维护缓冲区了,标准IO库会根据stdin/stdout来选择缓冲类型,也就是说当你使用标准IO的时候,要清楚它的stdin/stdout是什么类型以及其默认的缓冲模式,如果不合适,你需要用setvbuf先设置,再使用,例如协同进程的标准输入和输出的类型都是管道,所以其默认的缓冲类型是全缓冲的,如果要使用标准IO,就需要现设置行缓冲。
对于文件IO,只要你自己能维护好缓冲区,完全可以不用标准IO。

其次:从名字上来区分,文件I/O主要针对文件操作,读写硬盘等,标准I/O,主要是打印输出到屏幕等。因为他们设备不一样,文件IO针对的是文件,标准IO是对控制台,操作的是字符流。对于不同设备得特性不一样,必须有不同api访问才最高效。

3.函数

标准IO文件IO
打开fopenopen
关闭fcloseclose
fgets,gets,fgtec,getc,getchar,fread,fscanfread
fputs,puts,fputc,fputc,putchar,fwrite,fprintfwrite

(1).fopen与open

标准I/O使用fopen函数打开一个文件:

FILE* fp=fopen(const char* path,const char *mod)

其中path是文件名,mod用于指定文件打开的模式的字符串,比如"r",“w”,“w+”,"a"等等,可以加上字母b用以指定以二进制模式打开(对于 linux系统,只有一种文件类型,因此没有区别),如果成功打开,返回一个FILE文件指针,如果失败返回NULL,这里的文件指针并不是指向实际的文 件,而是一个关于文件信息的数据包,其中包括文件使用的缓冲区信息。

文件IO使用open函数用于打开一个文件:

int fd=open(char *name,int how);

与fopen类似,name表示文件名字符串,而how指定打开的模式:O_RDONLY(只读),O_WRONLY(只写),O_RDWR (可读可写),还有其他模式请man 2 open。成功返回一个正整数称为文件描述符,这与标准I/O显著不同,失败的话返回-1,与标准I/O返回NULL也是不同的。

(2).fclose与close

与打开文件相对的,标准I/O使用fclose关闭文件,将文件指针传入即可,如果成功关闭,返回0,否则返回EOF
比如:

if(fclose(fp)!=0)  
printf("Error in closing file");

而文件IO使用close用于关闭open打开的文件,与fclose类似,只不过当错误发生时返回的是-1,而不是EOF,成功关闭同样是返回0。C语言用error code来进行错误处理的传统做法。

(3).读写IO函数 (头文件:#include<stdio.h>)

字符读写函数 :fgetc和fputc

定义函数:int fputc(int c,FILE * stream);

函数说明:fputc 会将参数c 转为unsigned char 后写入参数stream 指定的文件中。

返回值:fputc()会返回写入成功的字符,即参数c。若返回EOF则代表写入失败。

定义函数:int fgetc(FILE * stream);

函数说明:fgetc()从参数stream所指的文件中读取一个字符。若读到文件尾而无数据时便返回EOF。

返回值:fgetc()会返回读取到的字符,若返回EOF则表示到了文件尾。

字符串读写函数:fgets和fputs

定义函数:int fputs(const char * s,FILE * stream);

函数说明:fputs()用来将参数s所指的字符串写入到参数stream所指的文件内。

返回值:若成功则返回写出的字符个数,返回EOF则表示有错误发生。

定义函数:char * fgets(char * s,int size,FILE * stream);

函数说明:fgets()用来从参数stream所指的文件内读入字符并存到参数s所指的内存空间,直到出现换行字符、读到文件尾或是已读了size-1个字符为止,最后会加上NULL作为字符串结束。

返回值:fgets()若成功则返回s指针即缓存地址,返回NULL则表示有错误发生。

char * gets(char * s);(不建议使用)
int puts(const char * s);

使用gets、fgets以字符串单位进行读取(读到遇到的第一个换行字符的后面),gets(接受一个参数,文件指针)不判断目标数组是否能够容纳读入的字符,可能导致存储溢出(不建议使用),而fgets使用三个参数char * fgets(char *s, int size, FILE *stream); 第一个参数和gets一样,用于存储输入的地址,第二个参数为整数,表示输入字符串的最大长度,最后一个参数就是文件指针,指向要读取的文件。

使用puts、fputs以字符串为单位写入,puts()只能向标准输出写,并且输出时会添加一个新行符。fputs()会原样输出。

数据块读写函数:fread和fwrite(全缓存读写函数)

定义函数: size_t fread(void * ptr,size_t size,size_t nmemb, FILE * stream)

函数说明: fread()用来从文件流中读取数据。

参数:stream为已打开的文件指针,ptr: 指向欲存放读取进来的数据空间,读取的字符数以参数size*nmemb来决定。

返回值:返回实际读取到的nmemb数目。

定义函数:size_t fwrite(const void * ptr, size_t size, size_t nmemb, FILE * stream)

函数说明:fwrite()用来将数据写入文件流中。

参数:stream:为已打开的文件指针,ptr: 指向欲写入的数据地址,总共写入的字符数以 参数size*nmemb来决定。

返回值:返回实际写入的nmemb数目。

格式化读写函数:fscanf和fprintf

定义函数:int fprintf(FILE * stream, const char * format,…)

函数说明:fprintf()会根据参数format字符串来转换并格式化数据,然后将结果输出到参数stream指定的文件中,直到出现字符串结束(‘\0’)为止。

返回值:成功则返回实际输出的字符数,失败则返回-1,错误原因存于errno中。

定义函数:Int fscanf(FILE * stream ,const char * format,…)

函数说明:fscanf()会自参数stream的文件流中读取字符串,再根据参数format字符串来转换并格式化数据。

返回值:成功则返回参数数目,失败则返回-1,错误原因存于errno中。

fscanf,与scanf类似,增加了一个参数用于指定操作的文件,比如fscanf(fp,"%s",words)

fprintf与printf类似,增加了一个参数用于指定写入的文件,比如:fprintf(stdout,“Hello %s.\n”,“dennis”);

切记fscanf和fprintf将FILE指针作为第一个参数,而putc,fputs则是作为第二个参数。

(4)区别读写错还是文件尾

定义函数: int feof(FILE * stream);

函数说明:feof()用来侦测是否读取到了文件尾,尾数stream为fopen()所返回之文件指针。
如果已到文件尾则返回非零值,其他情况返回0。

返回值:返回非零值代表已到达文件尾。

定义函数: long ftell(FILE * stream);

函数说明:ftell()用来取得文件流目前的读写位置。参数stream为已打开的文件指针。

返回值:当调用成功时则返回目前的读写位置,若有错误则返回-1,errno会存放错误代码。

错误代码:EBADF 参数stream无效或可移动读写位置的文件流。

(5)随机存取:fseek()、ftell()和lseek()
标准I/O使用fseek和ftell用于文件的随机存取,先看看fseek函数原型

int fseek(FILE *stream, long offset, int whence);

第一个参数是文件指针,第二个参数是一个long类型的偏移量(offset),表示从起始点开始移动的距离。
第三个参数就是用于指定起始点的模式
  stdio.h指定了下列模式常量:
  SEEK_SET 文件开始处
  SEEK_CUR 当前位置
  SEEK_END 文件结尾处
  看几个调用例子:

       fseek(fp,0L,SEEK_SET); //找到文件的开始处
    fseek(fp,0L,SEEK_END); //定位到文件结尾处
    fseek(fp,2L,SEEK_CUR); //文件当前位置向前移动2个字节数

而ftell函数用于返回文件的当前位置,返回类型是一个long类型,比如下面的调用:

      fseek(fp,0L,SEEK_END);//定位到结尾
    long last=ftell(fp); //返回当前位置

那么此时的last就是文件指针fp指向的文件的字节数。
  
 与标准I/O类似,linux系统提供了lseek来完成fseek的功能,原型如下:

off_t lseek(int fildes, off_t offset, int whence); 

fildes是文件描述符,而offset也是偏移量,whence同样是指定起始点模式,
唯一的不同是lseek有返回值,如果成功就 返回指针变化前的位置,否则返回-1。
whence的取值与fseek相同:SEEK_SET,SEEK_CUR,SEEK_END,但也可以用整数 0,1,2相应代替。

fseek和ftell == lseek;

4.标准IO的诟病

标准I/O最大的诟病是两次拷贝带来的性能开销。当读取数据时,标准I/O会向内核发起read()系统调用,把数据从内核中复制到标准I/O缓冲区。当应用通过标准I/O如fgetc()发起读请求时,又会拷贝数据,这次是从标准I/O缓冲区拷贝到指定缓冲区。写入请求刚好相反:数据先从指定缓冲区拷贝到标准I/O缓冲区,然后又通过write)函数,从标准I/O缓冲区写入内核。

避免两次拷贝的一个解决办法是,每个读请求返回一个指向标准I/O缓冲区的指针。这样,数据就可以直接从标准I/O缓冲区中读取,不需要多余的拷贝操作。如果应用确实需要把数据拷贝到自己本地缓冲区时(可能向其中写数据),总是可以手动地执行拷贝操作。这种实现方式需要提供资源“释放(free)”接口,允许应用在不用缓冲区时发出信号。

写请求会更复杂些,但是还是可以避免两次拷贝。当发起写请求时,记录指针位置,最终当准备将数据刷新到内核时,再通过记录的指针列表把数据写出去。这些可以通过分散-聚集I/O(scatter-gather I/O)模型的writev)函数来实现,这样写请求就只需要单个系统调用。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值