一、标准IO的效率

  对比以下四个程序的用户CPU、系统CPU与时钟时间对比

  程序1:系统IO

  

  程序2:标准IO getc版本

  

  程序3:标准IO fgets版本

  

  结果:

  

  【注:该表截取自APUE,上表中"表3-1中的最佳时间即《程序1》","表3-1中的单字节时间指的是《程序1》中BUFSIZE为1时运行时间结果",fgetc/fputc版本程序这里没放出】

  对于三个标准IO版本的每一个其用户CPU时间都大于最佳read版本,因为每次读一个字符版本中有一个要执行150万次的循环,而在每次读一行的版本中有一个要执行30000次的循环。而在read版本中,其循环只需执行180次。因为系统CPU时间都相同,所以用户CPU时间的差别造成了时钟时间的差别。系统CPU时间相同的原因是所有这些程序对内核提出的读写请求数相同。

  上表中最后一列是每个main函数的文本空间字节数(由c编译产生的机器指令)。从中可见getc/putc版本在文本空间做了大量宏替换,所以它所需的指令数超过了调用fgetc/fputc函数所用的指令数。从用户CPU时间看getc/putc版本与fgetc/fputc版本在此次测试中并没有多大的差别。

  使用每次一行IO的版本其速度大约是每次一个字符版本的两倍(包括用户CPU时间和时钟时间)。如果fgets/fputs函数用getc/putc实现则可以预计fgets版本的时间会与getc版本相接近。可以预料每次一行的版本会更慢一些,因为除了现存的60000次函数调用外还需增加3百万次宏调用。而在本测试中每次一行参数是用memccoy实现的,为了提高效率memccpy函数用汇编写。

  【重点】fgetc版本与程序1 BUFSIZE=1的版本要快得多,两者都用了约3百万次函数调用,造成速度差距这么大的原因在于《程序1》执行了3百万次函数调用这也执行了3百万次系统调用,而fgetc版本虽然执行了3百万次函数调用但是只引起了360次系统调用。系统调用与普通的函数调用相比是很耗时间的。

  

二、二进制IO

  为了可以读取二进制文件我们可以通过getc/putc实现的,但是这样必须循环整个结构。而fputs/fgets在遇到null字符时就结束,在结构中可能含有null字节,所以不能使用fgets/fputs。综上所以提供了下面两个函数以执行二进制IO操作

  

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const char *ptr, size_t size, size_t nobj, FILE *fp);
返回值:读或写的对象数

  常见的用法:

  • 读或写一个二进制数组。例如将一个浮点数组的第2至第5个元素写至一个文件上:

    float data[];
    if (fwrite(&data[], sizeof(float), , fp) != ) {
    fprintf(stderr, "fwrite error");
    }

    其中,指定size为每个数组元素的长度,nobj为欲写的元素数。

  • 读或写一个结构。例:

    struct {
    short count;
    long total;
    char name[NAMESIZE];
    } item; if (fwrite(&item, sizeof(item), , fp) != ) {
    fprintf(stderr, "fwrite error");
    }

  对于读,如果出错或到达文件尾,则fread返回的数字可能少于nobj。这时应该调用ferro+feof判断是哪种情况。对于写如果返回之小于nobj则出错。

  使用二进制IO的限制是只能用于读已写在同一系统上的数据。但是现在很多异构系统通过网络连接在一起,通常会在一个系统上读取另外一个系统上的数据,这样的话这两个函数就不能工作了,原因:

  • 在一个结构中,同一成员的位移量可能随编译程序和系统的不同而异,有些编译器会有优化选项以对齐或紧密包装结构(节省存储空间)以便在运行时易于存取结构中的各个成员。这意味着即使在单一系统中,一个结构的二进制存放方式也可能因编译器选项不通融而不同。
  • 用来存储多字节整数和浮点值的二进制格式在不同系统结构间也可能不同。  

三、 定位流

  有两种方式可以定位标准IO流。

  • ftell和fseek。这两个函数都假定文件的位置可以存放在一个长整型中。
  • fgetpos和fsetpos。这两个函数是ANSI C引入的。这两个函数引进了一个新的抽象数据类型fpos_t,它记录文件的位置。在非UNIX系统中,这种数据类型可以定义为记录一个文件的位置所需的长度。

  需要移植到非UNIX系统上运行的程序应使用fgetpos和fsetpos。

  

#include <stdio.h>

long ftell(FILE *fp); 返回值:成功则为当前位置相对于文件首的偏移字节数,出错为-1L
int fseek(FILE *fp, long offset, int whence); 返回值:成功为0,出错为非0
void rewind(FILE *fp);

  对于一个二进制文件,其位置指示是从文件起始位置开始度量并以字节为单位的。ftell用于二进制文件时,其返回值就是这种字节位置。为了用fseek定位一个二进制文件,必须指定一个字节offset,以及解释这种位移量的方式。whence与lseek函数相同:SEEK_SET表示从文件的起始位置开始,SEEK_CUR表示从当前位置,SEEK_END表示从文件的尾端。

  对于文本文件,它们的文件当前位置可能不以简单的字节位移量来度量。在非UNIX系统中可能以不同的格式存放文本文件,为了定位一个文本文件,whence一定要是SEEK_SET,而且offset只能有两种值:0(表示反绕文件到其起始位置),或者是对该文件的ftell所返回的值。使用rewind函数也可以将一个流设置到文件的起始位置。

#include <stdio.h>

int fgetpos(FILE *fp, fpos_t *pos);
int fsetpos(FILE *fp, const fpos_t *pos);
返回值:成功为0,出错非0

  fgetpos将当前位置存入pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重定向至该位置。

四、 格式化IO

  1. 格式化输出

#incldue <stdio.h>

in printf(const char *format, ...);
返回值:成功则为输出字符数,出错为负值
int fprintf(FILE *fp, const char *format, ...); 
返回值:成功则为输出字符数,出错为负值 int sprintf(char *buf, const char *format, ...);
返回值:存入数组的字符数

  sprintf将格式化的字符送入数组buf中。sprintf在该数组的尾端自动加一个null字节,但该字节不包含在返回值中。sprintf有可能会使buf指向的缓存溢出。

  printf族的三种变体类似于上面的三种,只不过是可变参数变成了arg

  

#include<stdarg.h>
#include<stdio.h>
int vprintf(const char * f o r m a t, va_list arg) ;
int vfprintf(FILE *f p, const char * f o r m a t, va_list arg) ;
两个函数返回:若成功则为输出字符数,若输出出错则为负值
int vsprintf(char *b u f, const char * f o r m a t, va_list arg) ;
返回:存入数组的字符数

  2. 格式化输入

  三个scanf函数:

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *format, ...);

  

五、实现细节

  在UNIX中标准IO最终都要调用系统IO。每个IO流都有一个与其关联的文件描述符,可以用fileno获取该流对应的文件描述符。

#include <stdio.h>
int fileno(FILE *fp);
返回值:与流相关联的文件描述符

  为了了解所使用的系统中标准IO的实现最好从stdio.h头文件开始。

  【注:原书中下面有一个案例这里没有放出】

六、临时文件

  标准IO库提供了两个函数以帮助创建临时文件

#include <stdio.h>

char *tmpnam(char *ptr);
返回值:指向一唯一路径名的指针
FILE *tmpfile(void);
返回值:成功则为文件指针,出错为NULL

  tmpnam产生一个与现在文件名(改文件名不是指ptr!该函数用来产生一个唯一文件)不同的一个有效路径名字符串。每次调用它时,它都产生一个不同的路径名,最多调用次数是TMP_MAX。TMP_MAX定义在<stdio.h>中

  若ptr是NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。下一次再调用tmpnam时会重写该静态区。(这意味着如果我们调用此函数多次,而且想保存路径名,那我们应该保存该路径名的副本而不是指针的副本) 如果ptr不是NULL,则认为它指向长度至少是L_tmpnam个字符的数组。(常数L_tmpnam定义在<stdio.h>中)所产生的路径名存放在该数组中,ptr也作为函数值返回。

  tmpfile创建一个临时二进制文件。在关闭该文件或程序结束时会自动删除这种文件。

  tempnam是tmpnam的一个变体,它允许调用者为所产生的路径名指定目录和前缀。

#include <stdio.h>
char *tempnam(const char *directory, const char *prefix);
返回值:指向一唯一路径名的指针

  对于目录有四种不同的选择:(优先级从高至低)

  (1) 如果定义了环境变量TMPDIR,则用其作为目录。

  (2) 如果参数directory非NULL,则用其作为目录。

  (3) 将<stdio.h>中的字符串P_tmpdir用作为目录。

  (4) 将本地目录,通常是/tmp用作为目录。

  如果prefix非NULL,则它通常是最多包含5个字符的字符串,用其作为文件名的前几个字符。

  该函数调用malloc函数分配动态存储区,用其存放所构造的路径名。当不再使用该路径名时就可释放次存储区。

[APUE]标准IO库(下)的更多相关文章

  1. [APUE]标准IO库(上)

    一.流和FILE对象 系统IO都是针对文件描述符,当打开一个文件时,即返回一个文件描述符,然后用该文件描述符来进行下面的操作,而对于标准IO库,它们的操作则是围绕流(stream)进行的. 当打开一个 ...

  2. C5 标准IO库:APUE 笔记

    C5 :标准IO库 在第三章中,所有IO函数都是围绕文件描述符展开,文件描述符用于后续IO操作.由于文件描述符相关的操作是不带缓冲的IO,需要操作者本人指定缓冲区分配.IO长度等,对设备环境要求一定的 ...

  3. 文件IO函数和标准IO库的区别

    摘自 http://blog.chinaunix.net/uid-26565142-id-3051729.html 1,文件IO函数,在Unix中,有如下5个:open,read,write,lsee ...

  4. 18、标准IO库详解及实例

    标准IO库是由Dennis Ritchie于1975年左右编写的,它是Mike Lestbain写的可移植IO库的主要修改版本,2010年以后, 标准IO库几乎没有进行什么修改.标准IO库处理了很多细 ...

  5. C++ Primer 读书笔记: 第8章 标准IO库

    第8章 标准IO库 8.1 面向对象的标准库 1. IO类型在三个独立的头文件中定义:iostream定义读写控制窗口的类型,fstream定义读写已命名文件的类型,而sstream所定义的类型则用于 ...

  6. 高级UNIX环境编程5 标准IO库

    标准IO库都围绕流进进行的 <stdio.h><wchar.h> memccpy 一般用汇编写的 ftell/fseek/ftello/fseeko/fgetpos/fsetp ...

  7. c++ primer 学习杂记3【标准IO库】

    第8章 标准IO库 发现书中一个错误,中文版p248 流状态的查询和控制,举了一个代码例子: int ival; // read cin and test only for EOF; loop is ...

  8. 第十三篇:带缓冲的IO( 标准IO库 )

    前言 在之前,学习了 read write 这样的不带缓冲IO函数. 而本文将讲解标准IO库中,带缓冲的IO函数. 为什么要有带缓冲IO函数 标准库提供的带缓冲IO函数是为了减少 read 和 wri ...

  9. 带缓冲的IO( 标准IO库 )

    前言 在之前,学习了 read write 这样的不带缓冲IO函数.而本文将讲解标准IO库中,带缓冲的IO函数. 为什么要有带缓冲IO函数 标准库提供的带缓冲IO函数是为了减少 read 和 writ ...

随机推荐

  1. 关于.NET异常处理的思考

    年关将至,对于大部分程序员来说,马上就可以闲下来一段时间了,然而在这个闲暇的时间里,唯有争论哪门语言更好可以消磨时光,估计最近会有很多关于java与.net的博文出现,我表示要作为一个吃瓜群众,静静的 ...

  2. ASP.NET Aries 入门开发教程7:DataGrid的行操作(主键操作区)

    前言: 抓紧勤奋,再接再励,预计共10篇来结束这个系列. 上一篇介绍:ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑 本篇介绍主键操作区相关内容. 1:什么时候有默认的 ...

  3. Spring之旅

    Java使得以模块化构建复杂应用系统成为可能,它为Applet而来,但为组件化而留. Spring是一个开源的框架,最早由Rod Johnson创建.Spring是为了解决企业级应用开发的复杂性而创建 ...

  4. Matlab 高脚杯模型切片

    前言:此文为去年我替人做的一个课题,觉得比较简洁,图形也比较美观,因此放在博文里 数据源我放到了百度云盘高脚杯数据源 有兴趣的可以下载数据,跑程序试一下.也可以单独看看代码,看下实现过程. 主函数 % ...

  5. 构建通用的 React 和 Node 应用

    这是一篇非常优秀的 React 教程,这篇文章对 React 组件.React Router 以及 Node 做了很好的梳理.我是 9 月份读的该文章,当时跟着教程做了一遍,收获很大.但是由于时间原因 ...

  6. jQuery学习之路(6)- 简单的表格应用

    ▓▓▓▓▓▓ 大致介绍 在CSS技术之前,网页的布局基本都是依靠表格制作,当有了CSS之后,表格就被很多设计师所抛弃,但是表格也有他的用武之地,比如数据列表,下面以表格中常见的几个应用来加深对jQue ...

  7. PHP的学习--RSA加密解密

    PHP服务端与客户端交互或者提供开放API时,通常需要对敏感的数据进行加密,这时候rsa非对称加密就能派上用处了. 举个通俗易懂的例子,假设我们再登录一个网站,发送账号和密码,请求被拦截了. 密码没加 ...

  8. ThinkPHP 模板substr的截取字符串函数

    ThinkPHP 模板substr的截取字符串函数在Common/function.php加上以下代码 /** ** 截取中文字符串 **/ function msubstr($str, $start ...

  9. 用Java代码实现拦截区域网数据包

    起因: 吃饭的时间在想如果区域网内都是通过路由器上网,那如何实现拦截整个区域网的数据包,从而实现某种窥探欲. 思路:      正常是通过电脑网卡预先设置或分配的IP+网关对路由器进行通讯,比如访问百 ...

  10. Android开发学习—— Broadcast广播接收者

    现实中:电台要发布消息,通过广播把消息广播出去,使用收音机,就可以收听广播,得知这条消息.Android中:系统在运行过程中,会产生许多事件,那么某些事件产生时,比如:电量改变.收发短信.拨打电话.屏 ...