缓冲IO&文件指针

今天听了网易公开课《C++程序设计(崔毅东老师)》的Unit7第一课,针对

1、缓冲IO

2、C语言中的文件指针

进行一下思考和讨论。

 

1、缓冲IO

摘自http://blog.csdn.net/carolzhang8406/article/details/7227761

不带缓存的I/O:       read,write,open......
标准(带缓存的)I/O: fgets,fread,fwrite.....

这里使用两个对应的函数进行比较:
ssize_t write(int filedes, const void *buff, size_t nbytes)
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp)

上面的buff和ptr都是指应用程序自己使用的buffer(就是说,在程序自己设计一个数组之类的,储存你想写入的信息),实际上当需要对文件进行写操作时,都会先写到内核所设的缓冲存储器。如果该缓存未满,则并不将其排入输出队列,直到缓存写满或者内核再次需要重新使用此缓存时才将其排入磁盘I/O输入队列,再进行实际的I/O操作,也就是此时才把数据真正写到磁盘,这种技术叫延迟写。

如果我们直接用非缓存I/O对内核的缓冲区进行读写,会产生许多管理不善而造成的麻烦(如一次性写入过多,或多次系统调用导致的效率低下)。

标准(带缓存的)I/O为我们解决了这些问题,它处理很多细节,如缓冲区分配,以优化长度执行I/O等,更便于我们使用。

由于标准(带缓存的)I/O在系统调用的上一层多加了一个缓冲区,也因此引入了流的概念,在UNIX/Linux下表示为FILE*(并不限于UNIX/Linux,ANSI C都有FILE的概念),FILE实际上包含了为管理流所需要的所有信息:实际I/O的文件描述符,指向流缓存的指针(标准I/O缓存,由malloc分配,又称为用户态进程空间的缓存,区别于内核所设的缓存),缓存长度,当前在缓存中的字节数,出错标志等。

因此可知,不带缓存的I/O对文件描述符操作,带缓存的标准I/O是针对流的

标准I/O对每个I/O流自动进行缓存管理(标准I/O函数通常调用malloc来分配缓存)。它提供了三种类型的缓存:
      1) 全缓存。当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的。
      2) 行缓存。当输入输出遇到新行符或缓存满时,才由标准I/O库执行实际I/O操作。stdin、stdout通常是行缓存的。
      3) 无缓存。相当于read、write了。stderr通常是无缓存的,因为它必须尽快输出。

一般而言,由系统选择缓存的长度,并自动分配。标准I/O库在关闭流的时候自动释放缓存。另外,也可以使用函数fflush()将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync()将所有内核缓冲区的数据写到文件(磁盘)。(小奇插个嘴:在一些嵌入式系统中,有时候不会让程序安全退出,而是直接断电,这样每过一段时间,就需要刷新一次,也就是fflush()&fsync()

在标准I/O库中也有引入缓存管理而带来的缺点--效率问题。例如当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准I/O缓存之间(当调用read和write时),第二次是在标准I/O缓存(通常系统分配和管理)和用户程序中的行缓存(fgets的参数就需要一个用户行缓存指针)之间。

 ***************************************************

带缓存的文件操作是标准C库的实现,第一次调用带缓存的文件操作函数时标准库会自动分配内存并且读出一段固定大小的内容存储在缓存中。所以以后每次的读写操作并不是针对硬盘上的文件直接进行的,而是针对内存中的缓存的。何时从硬盘中读取文件或者向硬盘中写入文件有标准库的机制控制。

内核<-->缓冲区<-->硬盘

 

 

不带缓存的文件操作通常都是系统提供的系统调用更加低级,直接从硬盘中读取和写入文件,由于IO瓶颈的原因,速度并不如意,而且原子操作需要程序员自己保证,但使用得当的话效率并不差。另外标准库中的带缓存文件IO 是调用系统提供的不带缓存IO实现的。

内核<-->硬盘

“术语不带缓冲指的是每个read和write都调用内核中的一个系统调用。所有的磁盘I/O都要经过内核的块缓冲(也称内核的缓冲区高速缓存),唯一例外的是对原始磁盘设备的I/O。既然read或write的数据都要被内核缓冲,那么术语“不带缓冲的I/O“指的是在用户的进程中对这两个函数不会自动缓冲,每次read或write就要进行一次系统调用。“--------摘自<unix环境编程>
 一、 标准I/O的缓存--标准输出为例:(这里都是指缺省情况下)
    1)当STDOUT连接到终端设备时,那么它就是行缓存的,也就是标准IO库没看到一个新行符 \n时就刷新一次缓存(即执行一次实际的输出操作)。这一特性可以通过如下测试代码来验证
#include "stdio.h"
#include "unistd.h"//file in Linux
//在windows下测试类似程序,效果不明显
int main()
{
    printf("The Line Should \nbe Cached");//前半句会立刻输出,后半句要等3s后再输出
    sleep(3);

    printf("\nThis Line Should be Cached again");
    //开头的\n帮助上面的“be Cached”输出了,但是后面This Line Should be Cached again无法输出
    sleep(3);
printf(
"This Line Should not be Cached again\n");//又过了3s,z这一行的文字,与上一行一起输出 sleep(3); return 0; }

 

  2)当STDOUT被重定向到一个具体文件时,那么标准输出是全缓存的,也就是说只有当输出缓存被塞满或者调用fflush或fclose时才会执行实际的写入操作,这里就不给出具体例子,可以通过freopen将STDOUT重定向到一个具体文件来进行测试。
   二、 标准出错STDERR:为了尽快的看到出错信息,标准出错是不带任何缓存的

 

2、C语言中的文件指针

文件指针并非指针,而是一个数据结构。

C语言中使用文件指针作为I/O的句柄。

文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。FILE *中除了包含了fd信息,还包含了IO缓冲,是C标准形式。

定义说明文件指针的一般形式为:
FILE *指针变量标识符;比如 File *fp;
其中FILE应为大写,它实际上是由系统定义的一个结构,该结构中含有文件名、文件状态和文件当前位置等信息。在编写源程序时不必关心FILE结构的细节。
在使用文件时,需要在内存中为其分配空间,用来存放文件的基本信息,给结构体类型是由系统定义的,C语言规定该类型为FILE型,其声明如下:
typedef struct
{
short level;
unsigned flags;
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer;
unsigned ar *curp;
unsigned istemp;
short token;
}FILE;

举个具体的实例(http://baike.baidu.com/link?url=W1BuR9MEAwfnz6M2m1MI6BOa4WL_r4YpLBzibIQ76a-dY-BMj7c1b0XJ1JbrEbDACpm5pNtS33rE8jn6ekjTgK)

#include<stdio.h>
#include<stdlib.h>
int main(intargc,char*argv[])
{
charbuf[1024];
FILE*fp=NULL;
if(argc!=2){
    fprintf(stderr,"argumenterror\ntry%sfilename\n",argv[1]);
    exit(1);
}
if((fp=fopen(argv[1],"r"))==NULL){
    perror("fopen");
    exit(1);
}
 
while(fgets(buf,1024,fp)!=NULL)
    printf("%s\n",buf);
 }
fclose(fp);
return0;
}

 

posted on 2015-07-08 22:28  依风152  阅读(314)  评论(0)    收藏  举报