[APUE]标准IO库(下)

一、标准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[10];
    if (fwrite(&data[2], sizeof(float), 4, fp) != 4) {
        fprintf(stderr, "fwrite error");
    }

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

  • 读或写一个结构。例:
    struct {
        short count;
        long total;
        char name[NAMESIZE];
    } item;
    
    if (fwrite(&item, sizeof(item), 1, fp) != 1) {
        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函数分配动态存储区,用其存放所构造的路径名。当不再使用该路径名时就可释放次存储区。

时间: 2024-10-12 03:00:16

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

读书笔记-APUE第三版-(5)标准IO库

ISO C标准I/O库使用流的概念读写文件.流是对数据传输的抽象,可以把流理解为从起点到终点间的字节序列. 标准I/O库通过维护进程空间内的缓冲区,减少read/write系统调用次数来提高I/O效率.之前介绍的Unbuffered I/O和文件描述符fd打交道,标准I/O则使用FILE指针. typedef struct{ short level;/*缓冲区满程度*/ unsigned flags;/*文件打开状态标志*/ char fd;/*文件描述符*/ unsigned char hol

Unix环境之标准IO库

UNIX的文件IO都是针对文件描述符的,而标准IO的操作则是围绕流进行的.当一个流最初被创建时,它并没有定向.若在未定向的流上使用一个多字节IO函数,则将该流的定向设置为宽定向:若在未定向的流上使用一个单字节IO函数,则将该流的定向设置为字节定向. stdio.h定义了三个标准流,stdin.stdout和stderr,分别是标准输入.标准输出和标准出错. 缓冲-- 标准IO库提供缓冲的目的是尽可能减少使用read和write调用的次数.它也对每个IO流自动地进行缓冲管理,从而避免了应用程序需要

嵌入式 Linux系统编程(三)——标准IO库

嵌入式 Linux系统编程(三)--标准IO库 与文件IO函数相类似,标准IO库中提供的是fopen.fclose.fread.fwrite等面向流对象的IO函数,这些函数在实现时本身就要调用linux的文件IO这些系统调用. 一.标准IO库函数的缓冲机制 由于IO设备的访问速度与CPU的速度相差好几个数量级,为了协调IO设备与CPU的速度的不匹配,对于块设备,内核使用了页高速缓存,即数据会先被拷贝到操作系统内核的页缓存区中,然后才会从操作系统内核的缓存区拷贝到应用程序的地址空间. 当应用程序尝

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

摘自 http://blog.chinaunix.net/uid-26565142-id-3051729.html 1,文件IO函数,在Unix中,有如下5个:open,read,write,lseek,close.称之为不带缓存的IO(unbuffered I/O).不带缓存指的是每个read,write都调用内核中的一个系统调用. 2,标准IO库,由ANSI C标准说明.标准IO库处理很多细节.例如缓存分配,以优化长度执行IO等.标准的IO提供了三种类型的缓存. (1)全缓存:当填满标准IO

《UNIX环境高级编程》--5 标准IO库

标准IO库 流和 FILE对象 标准IO库与文件IO区别: 标准IO库处理很多细节,如缓冲区分片.以优化的块长度执行IO等. 文件IO函数都是围绕文件描述符进行.首先打开一个文件,返回一个文件描述符:后续的文件IO操作都使用该文件描述符 标准IO库是围绕流进行的.当用标准IO库打开或者创建一个文件时,就有一个内建的流与之相关联 标准IO库的函数很多都是以 f开头,如fopen.fclose 对于ASCII字符集,一个字符用一个字节表示:对于国际字符集,一个字符可以用多个字节表示. 标准IO文件流

C++标准IO库

概述 先不要急着知道怎么用这个玩意,让我们一起先来看一看C++标准IO库的框架,其实挺有意思的!那就开始吧! C++的输入输出由标准库提供,标准库提供了一族类型,支持对文件.string对象.和控制窗口等设备的读写.一方面,这些IO类型都定义了如何读写内置类型的的值,另一方面,用户在设计类时可以仿照IO标准库设施读写内置类型的方式设计自己的输入输出操作. 1. 面向对象的IO库 面向对象是C++的一大特色,他的标准库自然也不例外,统统都是面向对象设计的.标准库使用了继承(inheritance)

标准IO库(详解)

文章转自:https://www.cnblogs.com/kingcat/archive/2012/05/09/2491847.html 自己在学习中,对此原文的基础之上进行补充. 什么是缓冲区 缓冲区又称为缓存,它是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区. 缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区. 为什么要引入缓冲区 比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算

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

第8章 标准IO库 8.1 面向对象的标准库 1. IO类型在三个独立的头文件中定义:iostream定义读写控制窗口的类型,fstream定义读写已命名文件的类型,而sstream所定义的类型则用于读写存储在内存中的string对象.在fstream和sstream里定义的美中类型都是从iostream头文件中定义的相关类型派生而来. 2. 流对象不能复制,因此不能存储在vector容器中 3. 形参或返回类型也不能为流类型.如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用.

《APUE》-第五章标准IO库

大多数UNIX应用程序都使用I/O库,本章说明了该库所包含的所有函数,以及某些实现细节和效率方面的考虑.同时需要重点关注标准I/O使用了缓冲的技术,但同时也是因为它的出现,产生了很多细节上的问题. 流和FILE对象 unix系统调用的函数都是针对文件描述符操作的.而标准I/O库,它们的操作则是围绕流进行的.当用标准I/O库打开或创建一个文件时,使一个流与一个文件相关联. 关于流定向的问题,当一个流刚被创建时,它并没有定向,我们可以在未定向的流上使用一个多字节I/O函数或者单字节的函数. #inc