《UNIXLinux程序设计教程》一2.7 流缓冲

2.7 流缓冲

每一个流都有一个输入输出缓冲区。写入流的字符并不立即写到文件中,而是先在缓冲区中聚集为一块,然后异步地以块为单位传送到文件。类似地,从流读出的字符也不是逐个地从文件中读出,而是以块为单位从文件读到缓冲区,然后从缓冲区传送给进程。这种处理方式称为缓冲。
采用缓冲的目的是为了减少调用低级I/O函数(如read()和write())的次数,因为这些真正读写文件的函数是系统调用,它们是较费时间的操作。例如,对于存储在硬盘上的文件,当进程用read()或write()读写数据时,设备驱动程序必须将数据在文件中的地址转换成硬盘的物理磁道号、卷宗号以及扇段号。之后设备必须移动磁头至相应的卷宗并等待磁盘的相应扇段旋转至磁头之下。一切准备好了之后才能从磁盘开始读写数据。显然,每读写一个或几个字符便导致执行这一串的动作是极不合算的。利用缓冲处理则不必为每读写一个字符而频繁地与外部设备打交道,同时还可以实现异步I/O,即在CPU运行程序的同时从外设传输数据,从而提高输入输出的效率。
标准I/O库函数自动地为我们管理缓冲区,使得我们无须过问何时该从文件中读一块数据至缓冲区和与特定设备有关的细节问题。
流有三种不同的缓冲类型:
1)全缓冲。在这种情况下,真正的I/O操作每次以整个缓冲区为单位读写数据,缓冲区的大小一般为BUFSIZ。对于输出,只有当缓冲区满了时才传送它至文件;对于输入,每次从文件读入数据直至缓冲区满为止。磁盘文件一般是全缓冲的。
2)行缓冲。在这种情况下,仅当在输入或输出中遇到换行符时才执行真正的I/O操作。行缓冲一般用于终端之类交互设备的流。例如,如果我们用fputc()输出15个非换行字符,然后输出一个换行符,则只有当最后这个输出换行符的fputc()被调用后,前面输出的 15个字符才能真正出现在终端上。
3)无缓冲。流不设置缓冲区,字符单个地读出或写入。
UNIX系统对新打开的流采用如下默认缓冲类型:
标准错误流总是无缓冲的。这是为了使得错误信息能及时显示出来。这意味着如果用fputc()输出15个字符至代表错误流的终端,则每一个字符都将在函数被执行后立即出现在终端上。
其他的流若引用交互设备则是行缓冲的,否则是全缓冲的。
这种自动默认选择给予输入输出文件或设备一种最方便的缓冲方式。不过,如果不满意这种默认缓冲的话,也可以用如下函数设定自己的缓冲区及希望的缓冲类型和大小。

#include <stdio.h>

void setbuf(FILE stream, char buf);
int setvbuf(FILE stream, char buf, int type, size_t size);

这两个函数必须在流已打开后且先于其他任何操作执行之前调用。
setbuf()用于打开或关闭流stream的缓冲。为了打开缓冲,参数buf必须指向一个长度为BUFSIZ的缓冲区。BUFSIZ 是系统定义的宏常数,它的值至少为256。通常在此函数调用之后流将变成全缓冲的,但如果流是与终端设备相连的话,则有的系统将改变它为行缓冲的。为了关闭缓冲,参数buf必须是NULL。
用setvbuf()可以明确地指定想要的缓冲类型。缓冲类型由参数type指定,它可取如下三种值之一,它们都是定义在中的常数。
_IOFBF 全缓冲
_IOLBF 行缓冲
_IONBF 无缓冲
如果指定无缓冲类型,setvbuf()将忽略参数buf和size;否则buf和size 可以任选地指定缓冲区及其大小。
如果用NULL作为buf的值,setvbuf()会自动地为流分配适当大小的缓冲区。所谓适当大小是指与此流相连文件的stat结构成员st_blksize指定的值(4.1.1节)。如果系统不能为流确定这个值(例如,当流与设备或管道相连时),则分配BUFSIZ长度的缓冲。当流被关闭时,这样分配的缓冲区将被自动释放。否则,buf应当是至少能容纳size个字符的一个数组。setvbuf()使用此数组作为流缓冲区,并释放标准I/O库原来分配的缓冲区。对于这个数组我们应当注意以下两点:
只要流是打开的,就不能释放该数组的空间。通常应当静态地分配此数组,或者用malloc()为它分配空间。用自动数组作为缓冲区是不好的,除非在退出说明该数组的程序块之前关闭文件。
流I/O函数将这个数组用于内部目的。当流正用它作为缓冲目的时,我们不能直接访问该数组的内容。
setbuf()实际上是setvbuf()的特例,它等价于
setvbuf(stream, buf, buf?_IOFBF:_IONBF, BUFSIZ);
术语刷新表示将缓冲区中的数据写出到文件中。通常,缓冲区中的数据在下述情况下会自动刷新:
1)当流被关闭时。
2)当调用exit()终止程序时(5.4节)。
3)若流是行缓冲的,当写出一换行符时。
4)当企图输出而缓冲区已经满了时。
5)无论何时对流的输入操作导致它实际从文件读数据时。
例如,在多数系统上,行缓冲区的大小通常是固定的,因此,如果在输出换行符之前一次输出的字符太多以致缓冲区满了时,尽管还未输出换行符,系统也会自动刷新缓冲区中的内容。这是上述第4种情形的例子。第5种情形的一个例子是:当用printf()输出不带换行符的一个字符串至终端之后,若紧接着调用从终端读数据的函数,则也导致缓冲区的输出立即被写到终端。这就是为什么用printf()输出不带换行符的字符串时,有时候它能立即出现在终端上(因为其后跟有输入操作),而有时候它却必须使用fflush()才行。
如果想在其他时刻刷新缓冲区的内容,则要显式地调用fflush()函数。

#include <stdio.h>

int fflush(FILE *stream);

fflush()刷新流stream的缓冲区。如果stream是一空指针NULL,fflush()将刷新所有已打开输出流的缓冲。
虽然标准I/O库函数自动地为我们管理I/O缓冲区,但让人感到迷惑然而也最简单的问题却常常是由缓冲引起的。例如,当设计用流进行输入输出的用户界面时,就必须了解流缓冲是怎样工作的,否则可能会发现输出(如显示程序进展或提示性的消息)并不像所预期的那样,甚至出现其他未曾料到的行为。
例2-8 程序2-8是一个由于未注意到缓冲的作用而导致输出行顺序不对的例子。
程序2-8 未注意缓冲作用导致错误之例

#include "ch02.h"
#include "y_or_n_ques.c"
int main()
{
   int answer;
   printf ("1: This is a buffer test program. ");
   // fflush(stdout);
   fprintf(stderr,"2: --test message\n");
   answer = y_or_n_ques("3: Hello, Are you a student?");
   if(answer == 1)      / 响应回答 /
      printf("4: Hope you have high score.");
   else
      printf("4: Hope you have good salary.");
   // fflush(stdout); */
   fprintf(stderr,"5: bye!\n");
}

这个程序简单地提出一个问题,然后对回答做出反应。程序的本意是想按照打印语句中第一个数字编号的顺序输出信息,但为了表现输出被缓冲的情况,我们在printf中故意没有加上换行符,并且在其后紧接着加入了向标准错误流输出的语句。由于标准错误流是无缓冲的,这使得它的输出将先于printf的输出而出现在终端上。运行这个程序有如下结果:

$ a.out
2: --test message
1: This is a buffer test program. 3: Hello, Are you a student? n
5: This is last line.
4: Hope you have good salary. $

这种结果不是我们预期的,我们原本希望按程序执行顺序输出每一行。为了如我们所愿,应当在输出中加入适当的换行符,或者在适当位置加入fflush()调用,例如,去掉程序中对fflush()调用的注释。

时间: 2024-11-02 00:30:02

《UNIXLinux程序设计教程》一2.7 流缓冲的相关文章

《UNIXLinux程序设计教程》一导读

前言 十年前,我们出版了<UNIX程序设计教程>(清华大学出版社).十年来,影响UNIX编程接口的规范和标准发生了较大变化,当时写书参照的"Single UNIX Specification 2"现在已发展到了"Single UNIX Specification 4",而若干分离独立的规范和标准,包括Single UNIX Specification,现在都已经统一在POSIX.1-2008标准之下.同时,随着Linux系统的成熟和发展,UNIX系统已不

《UNIXLinux程序设计教程》一2.4 读和写流

2.4 读和写流 一旦打开了一个流,就能对它进行读写,读写可以按无格式方式也可以按有格式方式进行.这一节介绍无格式I/O函数,下一节介绍有格式I/O函数.有以下三种类型的无格式I/O函数可供选择:1)字符I/O函数.这种函数每次读或写一个字符.2)行I/O函数.这种函数每次读写一行,每一行以换行符结束.3)块I/O函数.这种函数支持成块I/O,它们每次读写若干个对象,每个对象的大小是指定的.块I/O有时也称为二进制I/O.对象I/O或结构I/O. 2.4.1 字符I/O 如下三个字符输入函数每次

《UNIXLinux程序设计教程》一2.2 流和FILE对象

2.2 流和FILE对象 表示流的数据类型是FILE类型.FILE是系统定义的数据结构,它含有标准I/O库管理流所需要的与文件有关的所有内部状态信息,例如,进行实际I/O的文件描述字.文件位置指针.I/O缓冲区大小和指针.缓冲中当前存放的字符个数.错误和文件结束状态指示器等.FILE对象由标准I/O库函数内部分配和管理,用户无须自己创建FILE类型的对象,也不需要查看FILE对象的内容.当我们用fopen()打开或创建一个流时,它会返回一个指向FILE结构的指针,此时称在程序和该文件之间建立了一

《UNIXLinux程序设计教程》一2.5 文件定位

2.5 文件定位 读写文件过程中,有时会需要读某个特定位置的内容.例如,对于那种由固定大小的记录组成并能用整数索引来引用这些记录的文件,为访问其中某个特定的记录,最快捷的方法是直接定位至该记录位置进行读写,而不必一个一个地顺序跳过之前不需要的记录.为此,我们需要能够随意定位文件的位置,即随机地读写文件的任何部分. 标准I/O库提供了如下两组对随机文件进行定位的函数,用它们可以随机地读写文件的任何部分. #include <stdio.h> long int ftell(FILE *stream

《UNIXLinux程序设计教程》一第3章-3.0 低级输入输出

第3章-3.0 低级输入输出 标准I/O函数提供了丰富.便捷的输入输出方法,但有时程序员并不需要标准I/O函数提供的数据转换和缓冲处理,而希望使用自己的方法.例如,当需要用很大的缓冲来读二进制文件,或需要对特定设备(如终端)进行控制操作,或需要传递文件描述字给子进程(子进程可以用继承的文件描述字创建自己的流,但不能直接继承流)时,便需要使用UNIX的输入输出系统调用.这些系统调用习惯上称为低级I/O函数. 低级I/O函数对文件描述字进行操作,其中有一些是实现标准I/O函数的初等函数:另外一些则执

《UNIXLinux程序设计教程》一3.5 fdopen()和fileno()函数

3.5 fdopen()和fileno()函数 文件描述字函数是流函数的初等函数,每一个流都与一个描述字相连.给定一个打开的文件描述字,可以用fdopen()函数为它创建一个流.反过来,已知一个流,也可以用fileno()函数得到它的文件描述字. #include <stdio.h> FILE fdopen (int filedes, const char opentype); int fileno (file * stream); fdopen()使描述字filedes与一个流相连.它的返回

《UNIXLinux程序设计教程》一3.2 read()和write()函数

3.2 read()和write()函数 对文件描述字进行基本输入输出操作的函数是read()和write(). #include <unistd.h> ssize_t read (int filedes, void * buffer, size_t nbytes); ssize_t write (int filedes, const void * buffer, size_t nbytes); read()从已打开的.与文件描述字filedes相连的文件中读至多nbytes个字节的数据放到b

《UNIXLinux程序设计教程》一2.8 格式I/O

2.8 格式I/O 前几节介绍的流I/O函数除了以字符或行方式进行读写外,并不对数据进行解释,但在很多时候应用都会需要对输入输出数据进行解释,因为数据在计算机内的表示和人们可读的形式是不同的.数据在计算机内是二进制形式,在计算机外部常常为正文形式.例如,十进制数12在计算机内部的32位二进制表示是:00000000000000000000000000001100.当这个数在打印机上输出或者在终端屏幕上显示时,必须转换为字符'1'和'2',它们的ASCII编码分别为00110001和0011001

《UNIXLinux程序设计教程》一2.10 思考与练习

2.10 思考与练习 打开文件的实质是什么? 从应用的角度看,UNIX系统中程序与文件建立连接有几种机制?流与文件描述字有什么区别? 什么是文件位置?它起什么作用? 系统为每一个进程自动打开的输入输出流有哪些?它们对应的名字是什么? 写"r"方式打开的文件会发生什么情况?读"w"方式打开的文件呢?建议你编写一个这样的程序试试. 按读写数据的粒度分,有几类流输入输出函数? 为什么说gets()是危险的函数? 程序2-3说明了fgets()和gets()的不同.运行该程