对于标准I/O都是围绕流进行操作,当用标准I/O库打开一个文件时,使一个流与一个文件相关联。
流的定向决定了所读写的字符是单个字节还是多字节。当流创建时并没有定向,若在未定向的流上使用一个多字节I/O函数,则流设置为宽定向,若使用单字节函数,流定向为字节定向。
FILE通常是一个结构,它包含了标准I/O库管理流所需的信息:用于实际I/O文件的描述符,指向流缓冲区的指针,缓冲区长度,当前缓冲区的字符数以及出错标志。
标准IO流操作读写普通文件是使用全缓冲的,默认缓冲区长度是该文件系统优先选用的IO长度(从stat结构得到的st_blksize值)
缓冲
- 全缓冲(默认4096字节) :全缓冲指的是系统在填满标准IO缓冲区之后才进行实际的IO操作;注意,对于驻留在磁盘上的文件来说通常是由标准IO库实施全缓冲。
- 行缓冲 (默认1024字节):在这种情况下,标准IO在输入和输出中遇到换行符时执行IO操作,这允许我们一次输出一个字符,但是只有写了实际一行后才进行I/O操作;注意,当流涉及终端的时候,通常使用的是行缓冲。对于行缓冲有两个限制:一,因为标准I/O收集每一行缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写入一个换行符,也进行I/O操作。二,任何时候只要通过标准I/O库从<a>一个不带缓冲的流(需要从内核读取数据),<b>一个行缓冲的流(它从内核请求需要的数据——数据可能在缓冲区中,不需要从内核读取)得到输入数据,那么就会冲洗所有行缓冲输出的流
- 无缓冲 :无缓冲指的是标准IO库不对字符进行缓冲存储;注意,标准出错流stderr通常是无缓冲的。
冲洗:说明标准I/O缓冲区的写操作,缓冲区可能由标准I/O例程自动冲洗(当一个缓冲区满时)或者调用fflush冲洗一个流。在标准I/O方面,冲洗两层含义:1.将缓冲区中的内容写到磁盘上(缓冲区可能只是部分填满)2.驱动程序方面(tcflush)表示丢弃已存储在缓冲区中的内容。
缓冲特征:
- 标准输入和标准输出不指向交互式设备才指向全缓冲,指向交互设备是行缓冲
- 标准错误绝不是全缓冲
打开或关闭缓冲机制——这些函数一定要在流被打开后调用
#include <stdio.h> void setbuf(FILE *restrict fp, char *restrict buf); int setvbuf(FILE *restrict, char *restrict buf, int mode, size_t size); //成功返回0,出错返回非0
buff指向一个长度为BUFSIZ的缓冲区(定义在该头文件中),通常在此之后是全缓冲的,但是一个流如果与终端设备相关,某些设备也将其设置为行缓冲的,buf为NULL时关闭缓冲。
如果buff为NULL,标准I/O库自动为其分配适当的缓冲区长度,改长度由BUFSIZ指定。从函数返回之前关闭该流,缓冲区的一部分用于存放他自己的管理操作信息,所以缓冲区存放的字节数实际少于size。一般情况下,系统选择分配缓冲区的长度,并自动分配缓冲区,在这种情况下,关闭此流时,标准I/O库自动释放缓冲区。
setvbuf中的mode
- _IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。
- _IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。
- _IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。
强制冲洗缓冲
#include <stdio.h> int fflush(FILE *fp); //成功返回0出错返回非0
打开流
除非流引用终端设备,否则按系统默认,流被打开是全缓冲的,若引用终端设备则是行缓冲。
#include <stdio.h> FILE* fopen(const char* restrict pathname, const char* restrict type); FILE* freopen(const char* restrict pathname, const char* restrict type, FILE* restrict fp); FILE* fdopen(int filedes, char* type);
fopen打开一个指定的文件。
freopen在一直指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。如果该流已经定向,则freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定的流:标准输入,标准输出或标准出错。fdopen获取一个现有的文件描述符,并使一个标准的IO流与该描述符相结合。此函数常用与由创建管道和网络通信函数返回的描述符,因为这些特殊类型的文件不能用标准IO fopen函数打开,所以我们必须先调用设备专用函数获取一个文件描述符,然后用fdopen使一个标准IO流与该描述符相关联。
type参数指定对IO流的读写方式,详细如下表(使用b,使得标准IO可以区分文本文件和二进制文件)
注意:对于fdopen,type的意义稍有不同,因为该描述符已经被打开,所以fdopen为写而打开并不截短该文件,另外也不能创建该文件。
对于读写类型打开一个文件时(图中的+)
- 如果中间没有fflush,fseek,fsetpos,frewind则在输出的后面不能直接跟输入
- 如果中间没有fseek,setpos,rewind或一个输入操作没有到达文件尾端,输入操作后不能跟输出
总结一下上表的内容:
#include <stdio.h>
int fclose(FILE *fp);
在该文件被关闭前,冲洗缓冲区中的输出数据,缓冲区中的任何数据被丢弃,如果标准I/O库自动为流分配了一个缓冲区,则释放此缓冲区。
一个进程终止时(直接调用exit或从main中返回)所有带为写缓冲数据的标准I/O流都被冲洗,所有打开的I标准/O流都被关闭。
定位流
#include <stdio.h> long ftell(FILE *fp);//成功返回当前文件位置,失败返回-1L int fseek(FILE *fp,long offset,int whence);//成功返回0失败返回-1 void rewind(FILE *fp);//返回文件开头
- 二进制文件当前位置:文件位置指示器指向文件的起始位置,以字节为度量单位,ftell的返回值就是字节的位置。
- 文本文件当前位置:先将whence设置为SEEK_SET,offset设置为0或ftell返回值,然后调用ftell获取
内存流
使用第三方库的时候,我们需要处理某个文件,而这个文件不一定是从本地磁盘上读取,可能是分布式文件系统或者其他地方,而第三方库的接口却只提供了一个FILE *参数,意味着只能从磁盘加载,没法直接处理已经加载到内存的数据。这个时候,fmemopen就可以派上用场了,将FILE对象映射到内存上,无需从磁盘上读取。
#include <stdio.h> FILE* fmemopen(void*buf, size_t size, const char* mode); /* 1.无论何时以追加方式打开内存流,当前文件位置设置为缓冲区中的第一个NULL字节,如果缓冲区中不存在NULL字节,当前位置就设置为缓冲 区结尾的最后一个字节,当流不是以追加方式打开时。当前位置设置为缓冲区的开始位置,以为追加模式通过第一个NULL字节确定数据的尾端 不适合二进制数据(二进制数据在尾端之前可能包含多个NULL) 2.如果buf是一个null指针,打开流读写无意义,因为这种情况缓冲是通过fmemopen分配的,没有办法找到缓冲区的地址,以只写方式打开流 无法读入写入的数据,已读方式打开无法读取写入缓冲区的数据 3.任何时候只要增加缓冲区中的数据量以及调用fclose,fflush,fseek,fseeko,fsetpos都会在当前位置写个null字符 */ FILE* open_memstream(char**ptr, size_t* sizeloc);//面向字节 #include <wchar.h> FILE* open_wmemstream(wchar_t** ptr, size_t* sizeloc);//面向宽字节 //以上函数成功返回流指针,失败返回NULL
fmemopen()函数打开一个内存流,使你可以读取或写入由buf指定的缓冲区。其返回FILE*fp就是打开的内存流,虽然仍使用FILE指针进行访问,但其实并没有底层文件(并没有磁盘上的实际文件,因为打开的内存流fp是在内存中的),所有的I/O都是通过在缓冲区与主存(就是内存)之间来回传送字节来完成的。
后两个与第一个区别
- 创建的流只能写打开
- 不能指定自己的缓冲区,但可以分别通过bufp和sizep参数访问缓冲区地址和大小
- 关闭流后需要自行释放缓冲区
- 对流添加字节会增加缓冲区大小
缓冲区地址和大小使用的原则:
1.地址和长度只有调用fclose或fflush后才有效。2.这些值只有在下一次流写入或调用fclose前才有效,因为缓冲区可以增长,可能需要重新分配,如果出现这种情况,缓冲区的地址在下一次调用fclose或fflush时才会变。
当用fclose()关闭流或用fflush()刷新流时,bufp和sizep被更新,以包含缓冲区的指针及其大小。只要没有进一步的输出流发生,这些值仍然有效。如果执行额外的输出,必须再次刷新流来存储新的值,才能再次使用它们。一个空字符被写入缓冲区的末尾,但它存储在sizep中的size值中不包括它。
通过调用fmemopen()、open_memstream()或open_wmemstream()创建的一个与内存缓冲区相关联的流的输入和输出操作,发生在内存缓冲区的范围内,受限于实现。对于用open_memstream()或open_wmemstream()打开的流的情况,内存区域动态增长,以适应必要的写操作。对于输出,在刷新或关闭操作期间,数据从函数setvbuf()提供的缓冲区移动到内存流。如果没有足够的内存来增长内存区域,或者操作需要访问相关内存区域以外的地方,相关的操作失败。因为避免了缓冲区溢出,内存流非常适用于创建字符串。因为内存流只访问主存,不访问磁盘上的文件,所以对于把标准I/O流作为参数用于临时文件的函数来说,会有很大的性能提升。