这一章的主题是--缓冲式I/O
一,流与缓冲
流I/O是由C语言的标准函数提供的,这些I/O可以替代系统中提供的read和write函数。事实上流I/O的内
部封装了这两个基本的文件读写系统调用。使用流I/O在某些程度上来讲要方便一些,这些I/O在效率上没有
特别大的差异。
基于流的操作最终会调用read或者write函数进行操作。为了使程序的运行效率最高,流对象通常会提
供缓冲区,以减少调用系统I/O库函数的次数。
基于流的I/O提供以下2种缓冲:
1,全缓冲:直到缓冲区填满,才调用系统I/O函数。对于读操作来说,直到读入的内容的字节数等于缓冲区大
小或者文件以经到达结尾,才进行I/O操作将外存文件内容读入缓冲区;对于写操作来说,直到缓冲区填满,
才进行实际的I/O操作将缓冲区内容写到外存文件中。磁盘文件通常是全缓冲的。
2,行缓冲:直到遇到换行符\n才调用系统I/O函数。对于读操作来说,遇到换行符\n才进行I/O操作,将所
读内容写入缓冲区;对于写操作来说,遇到换行符\n才进行I/O操作,将缓冲区内容写到外存。由于缓冲区大
小是有限制的,所以当缓冲区填满时即使没有遇到\n,也同样会进行实际的I/O操作。标准输入stdin和标准
输出stdout都默认是行缓冲的。
3,无缓冲:没有缓冲区,数据会立即读入或者输出到外存文件和设备上。标准出错stderr是无缓冲的,这样
也能保证错误提示和输出能及时地反馈给用户,供用户排除错误。
二,基于文件流的操作
常用函数:
打开和关闭流
#include<stdio.h>
FILE*fopen(const char * restrict pathname,const char*restrict type);
FILE*fdopen(int fileds,const char*type);
fopen函数的第一个参数表示需要打开的文件的路径,第二个参数表示打开的方式。
fdopen函数用于在一个已经打开的文件上建立一个流,第一个参数是已打开文件的文件描述符,第二个参数是与fopen函数的第二个参数一样。只有一点不同的是,由于文件已经打开,所以fdopen函数不会再创建新文件,而且也不会将文件截短为0,这一点要热别注意,这两点在打开文件描述符的时候已经完成。
Type说明如下:
type |
文件类型 |
是否新建 |
是否清空 |
可读 |
可写 |
读写位置 |
r |
文本文件 |
NO |
NO |
YES |
NO |
文件开头 |
r+ |
文本文件 |
NO |
NO |
YES |
YES |
文件开头 |
w |
文本文件 |
YES |
YES |
NO |
YES |
文件开头 |
w+ |
文本文件 |
YES |
YES |
YES |
YES |
文件开头 |
a |
文本文件 |
NO |
NO |
NO |
YES |
文件结尾 |
a+ |
文本文件 |
YES |
NO |
YES |
YES |
文件结尾 |
rb |
二进制文件 |
NO |
NO |
YES |
NO |
文件开头 |
r+b或rb+ |
二进制文件 |
NO |
NO |
YES |
YES |
文件开头 |
wb |
二进制文件 |
YES |
YES |
NO |
YES |
文件开头 |
w+b或wb+ |
二进制文件 |
YES |
YES |
YES |
YES |
文件开头 |
ab |
二进制文件 |
NO |
NO |
NO |
YES |
文件结尾 |
a+b或ab+ |
二进制文件 |
YES |
NO |
YES |
YES |
文件结尾 |
Linux里用fclose函数关闭一个文件流,函数原型如下:
#include<stdio.h> int fclose(FILE *fp);
如果执行成功,函数返回0,失败返回EOF,这个值在定义在stdio.h中,其值为-1。fclose函数关闭文件时,该函数会将保存在内存中未来得及写回到磁盘的文件内容写回到磁盘上。了解这一点很重要,如果没有调用fclose函数,就必 须等待内存中缓冲区被填满,由系统将其内容写回到磁盘上去。对于fclose函数是否需要检查返回值的问题困扰着许多程序员。虽然严格地说应该检查所有的 系统调用的返回值,并且进行错误处理,但对于fclose函数出错的几率很小,几乎为0.但如果去关闭一个网络环境中的远程文件,fclose函数就有可 能出错。由于fclose函数在关闭文件时会将缓冲区的内容写回到磁盘上,因此fclose函数实际是进行了一个写操作。在网络环境中,文件的内容是要通 过网络传输到目的主机上并写入磁盘上的。在这个传输过程中,如果网络链接出现问题或者传输数据出错,就会导致文件内容写入失败。这时fclose函数就会 出错。由此可知,如果在本地关闭一个文件可以不用检查返回值;如果在网络环境中关闭一个文件,检查fclose函数的返回值是有必要的。
三,以字符为单位读写数据
每次读写一个字符数据的I/O方式称为每次一个字符的I/O。Linux下使用fgetc函数获得一个字符,其函数原型如下:
#include<stdio.h> int fgetc(FILE*fp);
函数如果执行成功则返回该字符的ASCLL值,如果执行失败,则返回EOF。
Linux环境下使用fputc函数输出一个字符数据,函数原型如下:
#include<stdio.h> int fputc(int c,FILE*fp)
第一个参数表示想要输出的字符的ASCLL值(源),第二个参数表示想要输出的文件流(目的地)。
四,以行为单位读写数据
当输入内容遇到\n时则将流中\n之前的内容送到缓冲区中的I/O方式称为每一次行的I/O。Linux使用下列函数提供一次读入一行的功能。
#include<stdio.h> char*fgets(char *restrict buf,int n,FILE*restrict fp); char*gets(char*);
fgets函数的第一个参数表示存放读入的缓冲区,第二个参数n表示读入的字符个数,此参数的最大值不能超过缓冲区的长度。fgets函数一直读,直到遇到\n为止,如果在n-1个字符內未遇到换行符,则只读入n-1个字符。最后一个字符用于存储字符串结束标志\0.需要注意的是fgets函数会将‘\n’换行符也读进缓冲区中,因此缓冲区的实际有效内容应该是缓冲区实际字节数(不包括‘\0’)减1.fgets函数的第三个参数是需要读入的流对象。
fgets函数的换回值有以下两种情况:1,成功读取一行,返回缓冲区的首地址。2,读取出错或者文件已经到达结尾则返回NULL。
gets函数和fgets函数类似,该函数从标准输入流中读取一行并将其存入一个缓冲区,并不将‘\n’读进缓冲区中。gets函数的返回值和fgets相同。
Linux 环境下用fputs函数和puts函数实现输出一行字符串,其函数原型如下:
#include<stdio.h> int fputs(const char*restrict str,FILE *restrict fp); int puts(const char*str);
puts函数的第一个参数表示存放输出内容的缓冲区,第二个参数表示要输出的文件。如果执行成功则返回输出的字节数,失败返回-1。puts函数用与向标准输出输出一行字符串,其参数和fputs函数的第一个参数相同,如果成功输出,则返回输出的字节数,失败则返回-1,值得注意的是,虽然gets函数不读入\n,但是puts函数却输出\n。fputs和puts函数都不输出字符串的结束符‘\0’。对于I/O来说,fputs函数和fgets函数的搭配是安全又可靠的。
五,gets函数的漏洞
gets函数和fgets函数最大的不同是gets函数的缓冲区虽然由用户提供,但是用户无法指定其一次最多读入多少个字节的内容。这一点导致gets函数变成了一个危险的函数。
六,二进制I/O
把数据写到文件效率最高的方法是用二进制形式写入。二进制输出避免了在数值转换为字符串过程中所涉及的开销和精度损失。但二进制数据并非人眼所能阅读。所以该方法只有当数据被另一个程序按顺序读取时才能使用。
fread函数用于读取二进制数据,fwrite函数用于写入二进制数据。
fread和fwrite
1.fread()和fwirte函数原型 size_t fread(void *buffer,size_t size,size_t count,FILE *stream); size_t fwirte(void *buffer,size_t size,size_t count,FILE *stream); 2.buffer是一个指向用于保存数据的内存位置的指针,size是缓冲区中每个元素的字节数,count是读取或写入的元素数,当然stream是数据读取或写入的留。 3.函数的返回值是实际读取或写入的元素(并非字节数目)。如果输入过程中遇到了文件末尾或者输出过程中出现了错误,这个数字可能比请求的元素数目要小。
注意和示例
在使用 fread 读二进制文件(png 图片)的时候, 发现读取到内存中的数据和 二进制文件中的数据不一致, 同样, 在 使用 fwrite 写二进制文件(png 图片)的时候, 发现写入到内存中的数据和 二进制文件中的数据和内存中的数据也不一致。
就是在读写二进制文件的时候,必须确保文件的打开形式是以 二进制读写的形式打开的, 即:文件的打开形式必须是 "rb", "wb" 要不然,读写数据的时候,就会出现错误。
/*
* 函数说明: 写二进制文件
* 参数描述: _fileName, 文件名称
* _buf, 要写的内存缓冲。
* _bufLen, 内存缓冲的长度
* 返回值: 0, 成功
* -1, 失败
*
*/
int writeFile(const STR* _fileName, void* _buf, int _bufLen)
{
FILE * fp = NULL;
if( NULL == _buf || _bufLen <= 0 ) return (-1);
fp = fopen(_fileName, "wb"); // 必须确保是以 二进制写入的形式打开
if( NULL == fp )
{
return (-1);
}
fwrite(_buf, _bufLen, 1, fp); //二进制写
fclose(fp);
fp = NULL;
return 0;
}
/*
* 函数说明: 读二进制文件
* 参数描述: _fileName, 文件名称
* _buf, 读出来的数据存放位置
* _bufLen, 数据的长度信息
* 返回值: 0, 成功
* -1, 失败
*
*/
int readFile(const char* _fileName, void* _buf, int _bufLen)
{
FILE* fp = NULL;
if( NULL == _buf || _bufLen <= 0 ) return (-1);
fp = fopen(_fileName, "rb"); // 必须确保是以 二进制读取的形式打开
if( NULL == fp )
{
return (-1);
}
fread(_buf, _bufLen, 1, fp); // 二进制读
fclose(fp);
return 0;
}