热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

LinuxSystemProgrammingChapterThree

这一章的主题是--缓冲式IO一,流与缓冲流IO是由C语言的标准函数提供的,这些IO可以替代系统中提供的read和write函数。事实上流IO的内部封装

这一章的主题是--缓冲式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是无缓冲的,这样

也能保证错误提示和输出能及时地反馈给用户,供用户排除错误。


二,基于文件流的操作

常用函数:

打开和关闭流#includeFILE*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函数关闭一个文件流,函数原型如下:

#includeint fclose(FILE *fp);

如果执行成功,函数返回0,失败返回EOF,这个值在定义在stdio.h中,其值为-1。fclose函数关闭文件时,该函数会将保存在内存中未来得及写回到磁盘的文件内容写回到磁盘上。了解这一点很重要,如果没有调用fclose函数,就必 须等待内存中缓冲区被填满,由系统将其内容写回到磁盘上去。对于fclose函数是否需要检查返回值的问题困扰着许多程序员。虽然严格地说应该检查所有的 系统调用的返回值,并且进行错误处理,但对于fclose函数出错的几率很小,几乎为0.但如果去关闭一个网络环境中的远程文件,fclose函数就有可 能出错。由于fclose函数在关闭文件时会将缓冲区的内容写回到磁盘上,因此fclose函数实际是进行了一个写操作。在网络环境中,文件的内容是要通 过网络传输到目的主机上并写入磁盘上的。在这个传输过程中,如果网络链接出现问题或者传输数据出错,就会导致文件内容写入失败。这时fclose函数就会 出错。由此可知,如果在本地关闭一个文件可以不用检查返回值;如果在网络环境中关闭一个文件,检查fclose函数的返回值是有必要的

三,以字符为单位读写数据

       每次读写一个字符数据的I/O方式称为每次一个字符的I/O。Linux下使用fgetc函数获得一个字符,其函数原型如下:

#includeint fgetc(FILE*fp);

函数如果执行成功则返回该字符的ASCLL值,如果执行失败,则返回EOF。

Linux环境下使用fputc函数输出一个字符数据,函数原型如下:

#includeint fputc(int c,FILE*fp)

第一个参数表示想要输出的字符的ASCLL值(源),第二个参数表示想要输出的文件流(目的地)。

四,以行为单位读写数据

      当输入内容遇到\n时则将流中\n之前的内容送到缓冲区中的I/O方式称为每一次行的I/O。Linux使用下列函数提供一次读入一行的功能。

#includechar*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函数实现输出一行字符串,其函数原型如下:

#includeint 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 &#61; NULL;if( NULL &#61;&#61; _buf || _bufLen <&#61; 0 ) return (-1);fp &#61; fopen(_fileName, "wb"); // 必须确保是以 二进制写入的形式打开if( NULL &#61;&#61; fp ){return (-1);}fwrite(_buf, _bufLen, 1, fp); //二进制写fclose(fp);fp &#61; NULL;return 0;
}/** 函数说明: 读二进制文件
* 参数描述: _fileName, 文件名称
* _buf, 读出来的数据存放位置
* _bufLen, 数据的长度信息
* 返回值: 0, 成功
* -1, 失败
*
*/
int readFile(const char* _fileName, void* _buf, int _bufLen)
{FILE* fp &#61; NULL;if( NULL &#61;&#61; _buf || _bufLen <&#61; 0 ) return (-1);fp &#61; fopen(_fileName, "rb"); // 必须确保是以 二进制读取的形式打开 if( NULL &#61;&#61; fp ){return (-1);}fread(_buf, _bufLen, 1, fp); // 二进制读fclose(fp);return 0;
}


fread函数和fwrite函数

1.函数功能

  用来读写一个数据块。

2.一般调用形式

  fread(buffer,size,count,fp);

  fwrite(buffer,size,count,fp);

3.说明

  &#xff08;1&#xff09;buffer&#xff1a;是一个指针&#xff0c;对fread来说&#xff0c;它是读入数据的存放地址。对fwrite来说&#xff0c;是要输出数据的地址。

  &#xff08;2&#xff09;size&#xff1a;要读写的字节数&#xff1b;

  &#xff08;3&#xff09;count:要进行读写多少个size字节的数据项&#xff1b;

  &#xff08;4&#xff09;fp:文件型指针。

#include #include int main(void){FILE *stream;char msg[] &#61; "this is a test";char buf[20];if ((stream &#61; fopen("DUMMY.FIL", "w&#43;"))&#61;&#61; NULL){fprintf(stderr,"Cannot open output file.\n");return 1;}/* write some data to the file */fwrite(msg, strlen(msg)&#43;1, 1, stream);/* seek to the beginning of the file */fseek(stream, SEEK_SET, 0);/* read the data and display it */fread(buf, 1, strlen(msg)&#43;1, stream);printf("%s\n", buf);fclose(stream);return 0;}
刷新一个流--fflush

函数名: fflush   
功 能: 清除文件缓冲区&#xff0c;文件以写方式打开时将缓冲区内容写入文件   
原型:int fflush(FILE *stream)
返回值&#xff1a;   
如果成功刷新,fflush返回0。指定的流没有缓冲区或者只读打开时也返回0值。返回EOF指出一个错误。   
注意:如果fflush返回EOF,数据可能由于写错误已经丢失。当设置一个重要错误处理器时,最安全的是用setvbuf函数关闭缓冲或者使用低级I/0例程,如open、close和write来代替流I/O函数。fflush()函数fflush(stdin)刷新标准输入缓冲区&#xff0c;把输入缓冲区里的东西丢弃   
fflush(stdout)刷新标准输出缓冲区&#xff0c;把输出缓冲区里的东西打印到标准输出设备上   
实例&#xff1a;

#include #include #include #include void flush(FILE *stream); int main(void) { FILE *stream; char msg[] &#61; "This is a test"; /* create a file */ stream &#61; fopen("DUMMY.FIL", "w"); /* write some data to the file */ fwrite(msg, strlen(msg), 1, stream); clrscr(); printf("Press any key to flush\ DUMMY.FIL:"); getch(); /* flush the data to DUMMY.FIL without\ closing it */ flush(stream); printf("\nFile was flushed, Press any key\ to quit:"); getch(); return 0; } void flush(FILE *stream) { int duphandle; /* flush the stream&#39;s internal buffer */ fflush(stream); /* make a duplicate file handle */ duphandle &#61; dup(fileno(stream)); /* close the duplicate handle to flush\ the DOS buffer */ close(duphandle); }
fflush 的返回值类型是int类型&#xff0c;那么这个int类型具体的返回是什么呢&#xff1f; 
  返回值&#xff1a; 
  如果成功刷新,fflush返回0。指定的流没有缓冲区或者只读打开时也返回0值。返回EOF指出一个错误。 
  注意:如果fflush返回EOF,数据可能由于写错误已经丢失。



下面介绍最后一个主题

错误和EOF

它是end of file的缩写&#xff0c;表示"文字流"&#xff08;stream&#xff09;的结尾。这里的"文字流"&#xff0c;可以是文件&#xff08;file&#xff09;&#xff0c;也可以是标准输入&#xff08;stdin&#xff09;。


比如&#xff0c;下面这段代码就表示&#xff0c;如果不是文件结尾&#xff0c;就把文件的内容复制到屏幕上。

 int c;while ((c &#61; fgetc(fp)) !&#61; EOF) {putchar (c);}

很自然地&#xff0c;我就以为&#xff0c;每个文件的结尾处&#xff0c;有一个叫做EOF的特殊字符&#xff0c;读取到这个字符&#xff0c;操作系统就认为文件结束了。

但是&#xff0c;后来我发现&#xff0c;EOF不是特殊字符&#xff0c;而是一个定义在头文件stdio.h的常量&#xff0c;一般等于-1。

 #define EOF (-1)

于是&#xff0c;我就困惑了。

如果EOF是一个特殊字符&#xff0c;那么假定每个文本文件的结尾都有一个EOF&#xff08;也就是-1&#xff09;&#xff0c;还是可以做到的&#xff0c;因为文本对应的ASCII码都是正值&#xff0c;不可能有负值。但是&#xff0c;二进制文件怎么办呢&#xff1f;怎么处理文件内部包含的-1呢&#xff1f;

这个问题让我想了很久&#xff0c;后来查了资料才知道&#xff0c;在Linux系统之中&#xff0c;EOF根本不是一个字符&#xff0c;而是当系统读取到文件结尾&#xff0c;所返回的一个信号值&#xff08;也就是-1&#xff09;。至于系统怎么知道文件的结尾&#xff0c;资料上说是通过比较文件的长度。

所以&#xff0c;处理文件可以写成下面这样&#xff1a;

int c;while ((c &#61; fgetc(fp)) !&#61; EOF) {do something}


这样写有一个问题。fgetc()不仅是遇到文件结尾时返回EOF&#xff0c;而且当发生错误时&#xff0c;也会返回EOF。因此&#xff0c;C语言又提供了feof()函数&#xff0c;用来保证确实是到了文件结尾。上面的代码feof()版本的写法就是&#xff1a;

int c;while (!feof(fp)) {c &#61; fgetc(fp);do something;}

但是&#xff0c;这样写也有问题。fgetc()读取文件的最后一个字符以后&#xff0c;C语言的feof()函数依然返回0&#xff0c;表明没有到达文件结尾&#xff1b;只有当fgetc()向后再读取一个字符&#xff08;即越过最后一个字符&#xff09;&#xff0c;feof()才会返回一个非零值&#xff0c;表示到达文件结尾。

所以&#xff0c;按照上面这样写法&#xff0c;如果一个文件含有n个字符&#xff0c;那么while循环的内部操作会运行n&#43;1次。所以&#xff0c;最保险的写法是像下面这样&#xff1a;

int c &#61; fgetc(fp);while (c !&#61; EOF) {do something;c &#61; fgetc(fp);}if (feof(fp)) {printf("\n End of file reached.");} else {printf("\n Something went wrong.");}


除了表示文件结尾&#xff0c;EOF还可以表示标准输入的结尾。

int c;while ((c &#61; getchar()) !&#61; EOF) {putchar(c);}

但是&#xff0c;标准输入与文件不一样&#xff0c;无法事先知道输入的长度&#xff0c;必须手动输入一个字符&#xff0c;表示到达EOF。

Linux中&#xff0c;在新的一行的开头&#xff0c;按下Ctrl-D&#xff0c;就代表EOF&#xff08;如果在一行的中间按下Ctrl-D&#xff0c;则表示输出"标准输入"的缓存区&#xff0c;所以这时必须按两次Ctrl-D&#xff09;&#xff1b;Windows中&#xff0c;Ctrl-Z表示EOF。&#xff08;顺便提一句&#xff0c;Linux中按下Ctrl-Z&#xff0c;表示将该进程中断&#xff0c;在后台挂起&#xff0c;用fg命令可以重新切回到前台&#xff1b;按下Ctrl-C表示终止该进程。&#xff09;

那么&#xff0c;如果真的想输入Ctrl-D怎么办&#xff1f;这时必须先按下Ctrl-V&#xff0c;然后就可以输入Ctrl-D&#xff0c;系统就不会认为这是EOF信号。Ctrl-V表示按"字面含义"解读下一个输入&#xff0c;要是想按"字面含义"输入Ctrl-V&#xff0c;连续输入两次就行了。






推荐阅读
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • C# WPF自定义按钮的方法
    本文介绍了在C# WPF中实现自定义按钮的方法,包括使用图片作为按钮背景、自定义鼠标进入效果、自定义按压效果和自定义禁用效果。通过创建CustomButton.cs类和ButtonStyles.xaml资源文件,设计按钮的Style并添加所需的依赖属性,可以实现自定义按钮的效果。示例代码在ButtonStyles.xaml中给出。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • svnWebUI:一款现代化的svn服务端管理软件
    svnWebUI是一款图形化管理服务端Subversion的配置工具,适用于非程序员使用。它解决了svn用户和权限配置繁琐且不便的问题,提供了现代化的web界面,让svn服务端管理变得轻松。演示地址:http://svn.nginxwebui.cn:6060。 ... [详细]
  • Linux环境变量$PATH的作用及使用方法
    本文介绍了Linux环境变量$PATH的作用及使用方法。$PATH是一个由多个目录组成的变量,用冒号分隔。当执行一个指令时,系统会按照$PATH定义的目录顺序搜索同名的可执行文件,如果有多个同名指令,则先找到的会被执行。通过设置$PATH变量,可以在任何地方执行指令,无需输入绝对路径。 ... [详细]
  • Python中sys模块的功能及用法详解
    本文详细介绍了Python中sys模块的功能及用法,包括对解释器参数和功能的访问、命令行参数列表、字节顺序指示符、编译模块名称等。同时还介绍了sys模块中的新功能和call_tracing函数的用法。推荐学习《Python教程》以深入了解。 ... [详细]
  • 本文介绍了禅道作为一款国产开源免费的测试管理工具的特点和功能,并提供了禅道的搭建和调试方法。禅道是一款B/S结构的项目管理工具,可以实现组织管理、后台管理、产品管理、项目管理和测试管理等功能。同时,本文还介绍了其他软件测试相关工具,如功能自动化工具和性能自动化工具,以及白盒测试工具的使用。通过本文的阅读,读者可以了解禅道的基本使用方法和优势,从而更好地进行测试管理工作。 ... [详细]
author-avatar
一大书虫
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有