一、标准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. javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈

    Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...

  2. 在SQL2008查找某数据库中的列是否存在某个值

    在SQL2008查找某数据库中的列是否存在某个值 --SQL2008查找某数据库中的列是否存在某个值 create proc spFind_Column_In_DB ( @type int,--类型: ...

  3. 解决PHP-问题:Class 'SimpleXMLElement' not found in

    1.问题 在ubuntu 16.10中,学习PHP,学习到PHP如何生成XML文件时候,碰到了这个问题: PHP Fatal error: Class 'ClassName\SimpleXMLElem ...

  4. C# 多种方式发送邮件(附帮助类)

    因项目业务需要,需要做一个发送邮件功能,查了下资料,整了整,汇总如下,亲测可用- QQ邮箱发送邮件 #region 发送邮箱 try { MailMessage mail = new MailMess ...

  5. [C#] 剖析 AssemblyInfo.cs - 了解常用的特性 Attribute

    剖析 AssemblyInfo.cs - 了解常用的特性 Attribute [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5944391.html 序 ...

  6. jquery屏幕滚动计算事件总结

    获取浏览器显示区域(可视区域)的高度 : $(window).height(); 获取浏览器显示区域(可视区域)的宽度 : $(window).width(); 获取页面的文档高度: $(docume ...

  7. Entity Framework 延伸系列目录

    1.采用MiniProfiler监控EF与.NET MVC项目 2.采用EntityFramework.Extended 对EF进行扩展 3.EntityFramework执行存储过程中遇到的那些坑 ...

  8. Python学习

    Python基础教程        网易云课堂-零基础入门学习Python

  9. Java程序员应该了解的10个面向对象设计原则

    面向对象设计原则: 是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorat ...

  10. DBobjectsCompareScript(数据库对象比较).sql

    use master goIF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[func_CompareDBobjectsReColu ...