天天看点

一个例子叫你理解缓冲输入与非缓冲输入,以及流的概念:论read与fget open与fopen的区别一、缓冲输入与非缓冲输入。

首先讲一下基本概念

本文假设你已经基本了解标准IO库函数fopen fgets setbuf等和非缓冲IO库函数open write read lseek等。这里只就他们的区别加以阐述,并以最简单的程序实现他们之间差异的体现。让你理解流,缓冲,非缓冲的概念。

一、缓冲输入与非缓冲输入。

fopen,fgets等属于缓冲输入,open,write,read系列属于非缓冲。

APUE上的解释:

unbuffered IO是指在每个read和write都调用内核中的一个系统调用,他们不是ISO C的组成部分,但属于POSIX.1

标准IO库兼容各种系统,是带缓冲的输入输出。属于高级库。由ISO C标准定义,在linux上还做了一点扩展,实现了部分只支持linux的接口 比如 fdopen。

从网上得到的一些值得参考的解释:

       1. 缓冲文件系统

       缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用,当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”, 装满后再从内存“缓冲区”依此读入接收的变量。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存 “缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。

     非缓冲文件系统

缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数 据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度 快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。open, close, read, write, getc, getchar, putc, putchar 等。

 (这是一个文件系统层面的缓冲概念)

2. open返回一个文件描述符,文件描述符是linux下的一个概念,linux下的一切设备都是以文件的形式操作.如网络套接字、硬件设备等。当然包括操作文件。

fopen是标准c函数。返回文件流而不是linux下文件句柄。

3. 设备文件不可以当成流式文件来用,只能用open

fopen是用来操纵正规文件的,并且设有缓冲的,跟open还是有一些区别

一般用fopen打开普通文件,用open打开设备文件

fopen是标准c里的,而open是linux的系统调用.

他们的层次不同.

fopen可移植,open不能。

4. fopen和open最主要的区别是fopen在用户态下就有了缓存,在进行read和write的时候减少了用户态和内核态的切换,而open则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列快;如果随机访问文件open要比fopen快。

这一回答将fopen 与open的区别解释的非常到位

5.区别总结

open,fopen

前者属于低级IO,后者是高级IO。

前者返回一个文件描述符(用户程序区的),后者返回一个文件指针。

前者无缓冲,后者有缓冲。

前者与 read, write 等配合使用, 后者与 fread, fwrite等配合使用。

后者是在前者的基础上扩充而来的,在大多数情况下,用后者。

在linux下fopen函数系列的实现是对open系统调用系列的封装。

     以键盘输入为例,过程是这样的:  

    键盘中断 -> driver与硬件交互获取键值 -> driver将键值上报给kernel -> kernel 通过缓冲队列的形式(linux下应该是input子系统)管理得到的消息,并上报给应用层 ->  系统调用 读取到键值,存入文件或者显示到屏幕上。

二、fgets,read

由于fgets函数比较个性,还是要稍微介绍一下他的特殊之处:char *fgets(char *buf, int bufsize, FILE *stream);

1、他是操作文件流的函数 *FILE stream

2、每次最多能读取bufsize - 1个字符,然后在最后还会加上'\0'

3、每次读取最多一行,且换行符'\n'会被存入buff中,也就是说读取一行时 buffsize大于strlen + 2 (‘\n’‘\0’)

read函数很单纯哒,我给一个原型就好啦,剩下的自己去看manual吧

int read(int handle, void *buf, int nbyte);

1、操作文件描述符fd的函数

三、fflush,rewind,setbuf,lseek

前三个是操作流的,lseek是操作文件描述符的。

代码分析:(需要加载的头文件我都不写了,自己添加)

代码1:

int main (void)

{

char buff[10];

read(0,buff,5);//标准输入的文件描述符为0

write(1,buff,5);//  标准输出的文件描述符为1

exit(0);

}

输入: 12345678回车  //输入超过了read的size

输出:12345

[email protected]:~$678

678: command not found

解释:首先write是正常工作了的,buff内存储的是{1,2,3,4,5后面是空的}

但是在标准输入中还缓存着 678\n

程序结束后,缓存会被清空(冲洗 flush)678\n这几个字符就会在程序结束时强制输出到terminal中也就成了你输入了一个命令678,但这并不是命令。

改进代码1

int main (void)

{

char buff[10];

read(0,buff,5);//键盘输入的文件描述符为0

write(1,buff,5);//  显示屏输出的文件描述符为1

//fflush(stdin);

//rewind(stdin);

//setbuf(stdin,NULL);

char a = getchar();

putchar(a);

//read(0,buff,5); //特别注意如果这里再加上一个read 那么,在第一次输入后程序不会结束,会阻塞直到再次输入,这是因为标准输入流已经把kernal那里的东西全都读到ISO C层的buff内了,所以read这种不能从ISO层buff得到数据的非缓冲函数就无法获得数据了,只有阻塞直到新的数据输入。把这个小程序熟悉一下,你基本就明白流和文件描述符,缓冲与非缓冲输入的区别了。

exit(0);

}

输入: 12345678回车  //输入超过了read的size

输出:[email protected]:

分析被注释掉的fflush,rewind,setbuf都是可以清空缓存的函数,但如果此处使用,不会有任何作用,因为这几个函数是针对文件流的缓存,即构建在系统调用层之上的缓存,目的是提前预加载文件减少系统调用次数,内核实现的键盘输出缓存并不在他们可操作范围之内,read是没有缓存的,他是直接的系统调用。而getchar是有缓存的函数,调用getchar后ISO C层会以文件流的形式打开打开标准输入文件,接收标准输入传来的数据,存入文件流的缓存中,所以这时标准输入那边的缓存就空了。而ISO层的缓存是系统调用层之上的缓存,所以在程序结束时释放缓存是不会在终端打印字符的。当然如果是以清除缓存为目的显然getchar没有达到目的,应该写为while((a = getchar() != EOF) && a != '\n')

改进代码3:

了解了缓冲输入与非缓冲输入的区别后我们知道还可以把read改写成fgets这样一来setbuf就可以清除缓存了(当然这时不清除缓存也不会出现将多余字符输出到命令行中的情况了),但是fflush和rewind还是不能用,为什么呢?因为这里是linux。。。。如果是在windows下是可以的。这只是标准实现时的区别,不要在意这些细节。注意sefbuf(stdin,NULL)并不是清空缓存这么简单而是将stdin设置为无缓存模式。。。所以虽然有清除缓存的功能,但是其实并不是清除缓存函数。

int main (void)

{

char buff[10];

fgets(buff,5,stdin);//键盘输入的文件描述符为0

write(1,buff,5);//  显示屏输出的文件描述符为1

//fflush(stdin);

//rewind(stdin);

setbuf(stdin,NULL);

exit(0);

}

输入: 12345678回车  //输入超过了read的size

输出:[email protected]:

继续阅读