[ APUE ] 第五章 文件和目录


2.流和FILE对象

在前几章的所有的IO函数中都是围绕着“文件描述符”进行的。而对于标准IO库,它们的操作是围绕着“流(stream)”进行的。用标准IO库打开或者创建一个文件,就会使一个流与一个文件相关联。

流的定向(stream's orientation)决定了所读写的字符是单字节还是多字节的。流刚创建时是未定向的。在未定向的流上使用单字节还是多字节(wchar.h)的IO函数,会将流的定向设置为单字节或多字节。

只有两个函数可以改变流的定向:freopen和fwide,分别用于清除流定向和设置流定向

#include 

int fwide(FILE *stream, int mode);

返回值:正:宽定向 负:字节定向 零:未定向。

mode为正:宽定向

mode为负:字节定向

mode为0:不设置定向,返回该流定向的值

fwide并不改变已经定向的流的定向!

注意fwide返回值没有涉及到出错返回,那么当流是无效的呢?我们可以在调用fwide之前清空errno,在返回后判断errno的值。

当我们使用fopen打开一个文件时会返回一个FILE指针,这个指针指向一个结构体,包含了管理这个流的所有信息。通常包括:实际IO用的文件描述符(还是得用文件描述符)、指向缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等等。

3.标准输入、标准输出和标准错误

STDIN_FILENO , STDOUT_FILENO , STDERR_FILENO 三个文件描述符,数字分别是0,1,2。

还有他们各自对应的FILE指针,stdin、stdout、stderr。这三个指针在

4.缓冲

标准库提供缓冲是为了减少read和write的调用次数,从而提高效率。

  1. 全缓冲,当缓冲区填满后才进行实际IO操作。对于驻留磁盘上的文件通常使用全缓冲。缓冲区的获取一般通过malloc获得。

    flush是标准IO缓冲区的写操作。缓冲区可以由标准IO自动冲洗,也可调用fflush冲洗。

    例如fflush可以将缓冲区内容写到磁盘上。(还记得fflush(stdout)么,可以将缓冲区内容冲到屏幕上。

  2. 行缓冲。在这种情况下遇到换行符标准IO库就执行IO操作。抑或当缓冲区满的时候也进行IO操作。

  3. 不带缓冲。

stderr通常是不带缓冲的,它会让出错信息尽快输出。

ISO C要求:

  • 当且仅当标准输入和标准输出不指向交互式设备室,他们才是全缓冲的。
  • 标准错误绝不会是全缓冲的。

setbuf函数和setvbuf函数可以改变缓冲类型。

#include 

void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

成功返回0,不成功返回非0

setbuf用于打开或关闭缓冲机制。关闭缓冲buf为NULL,否则为缓冲区指针。

setvbuf可以指定缓冲类型,mode参数如下:

_IOFBF 全缓冲

_IOLBF 行缓冲

_IONBF 不带缓冲

如果指定不带缓冲就忽略buf和size(为0),如果buf是NULL,mode指定带缓冲,则标准IO库将自动分配缓冲区,大小为参数size。

如果buf是函数的局部栈变量,则函数返回前必须关闭相关流!(一般没人这么干吧。。

某些实现可能会在缓冲区里存一些管理信息,所以缓冲区实际可用大小小于size。

fflush参数为NULL可以冲洗所有输出流。

5.打开流

#include 

FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);

freopen在指定流上打开指定文件,若流已经打开则先关闭该流(UNIX下打开文件会自动分配到数值最小的空闲描述符上——《unix编程实践教程》),若已经定向则清除定向(害记得第一次爆零开火车??

fdopen函数取一个已有的文件描述符(比如open/dup/pipe/socket获得的等等)并让标准IO流与该描述符组合。他的mode不能O_TRUNC和通过追加创建,因为他的描述符只为写打开且文件已存在。

fclose关闭流。会冲洗缓冲中的输出数据,缓冲数据被丢弃,自动分配的会被释放。

当一个进程终止时所有还没写的缓冲数据都被冲洗,所有打开的标准IO流都被关闭。

6.读和写流

一旦流打开,则可以在三种不同类型的非格式化IO中进行选择,对其进行读写操作。

  1. 每次一个字符IO,一次读或写一个字符。
  2. 每次一行IO,每次读或写一行,则使用fgets和fputs,以换行符终止。
  3. 直接IO,fread和fwrite,二进制IO。每次IO读或写某种数量的对象,而对象具有指定长度。

输入函数

#include 

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

单个字符读取。成功返回下一个字符,若已到达文件末端或出错返回EOF。

getchar等于getc(stdin)。getc可以用宏实现,fgetc不行。

  1. getc参数不能为有副作用的表达式,因为可能被计算多次。
  2. fgetc肯定是一个函数,所以可以得到地址,这就允许将fgetc的地址作为参数传递给另一个函数。
  3. 调用fgetc所需时间比getc长。

无论是出错还是到文件末端都返回EOF,所以为了区分不同情况,我们必须调用ferror或feof。

#include 

void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);

真返回非0,假返回0.

clearerr可以清空FILE对象中的出错标志和文件结束标志。

ungetc函数可以将字符压回流中

#include 
int ungetc(int c, FILE *stream);

压回顺序与读出顺序相反,一次成功的ungetc调用会清除该流的文件结束标志。

输出函数

#include 

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

putchar(c) = putc(c,stdout),putc可被实现为宏,而fputc不可以。

7.每次一行IO

输入

#include 

char *fgets(char *s, int size, FILE *stream);

fgets一直读到换行符(会将换行符存入缓冲区),但不超过size-1个字符,缓冲区总是以空字符结尾。

gets早就死了(不会将换行符存入缓冲区)。

输出

#include 

int fputs(const char *s, FILE *stream);
int puts(const char *s);

fputs将以空字符结尾的字符串写到指定流,空字符不写出。但是fputs可能写不止一行,因为字符串中可能有换行符!

puts将以空字符结尾的字符串写到标准输出,空字符不写出,但会写出一个换行符。

puts还是避免使用。。因为要记得是否添加了换行符。

8.IO效率

![image-20200807180429510](第五章 标准IO.assets/image-20200807180429510.png)

fgetc比read【size = 1】要快得多,因为系统调用次数要少得多(两亿和两万)(缓冲区标准IO会自动设置合适大小)

系统调用比函数调用更花时间!

9. 二进制IO

fputs遇到空字符停止,fgets遇到换行符就停止了。

有时候我们不想这样就停了,所以我们需要二进制IO操作。

#include 

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
              FILE *stream);

返回读或写的对象数。如果读返回值小于nmemb可以用ferror或feof判断情况。如果写返回值小于nmemb则出错。

size是对象大小,nmemb是对象个数。

(是时候学一手快读了

10.定位流

移植到非UNIX系统的程序应当使用fgetpos和fsetpos,他们是ISO C下的。

#include 

int fseek(FILE *stream, long offset, int whence);
										成功返回0,出错返回-1
long ftell(FILE *stream);
                                        成功返回当前文件位置指示,出错返回-1
void rewind(FILE *stream);
可以将流设置到起始位置

whence:SEEK_SET表示从文件起始位置开始,SEET_CUR表示从当前文件位置开始,SEEK_END表示从文件尾端开始。SEEK_END在UNIX下可用,某些系统不一定可用(某些系统要求二进制文件为幻数整数倍,不满则填充0)。

int fgetpos(FILE *stream, fpos_t *pos);

int fsetpos(FILE *stream, const fpos_t *pos);
										成功返回0,不成功返回非0

fgetpos将文件指示的位置存入pos,fsetpos将文件指示定位至该位置。

fpos_t可以存下很大的数,相较于fseek,ftell的整形和长整型还是强了太多。

11.格式化IO

喜闻乐见的printf和scanf函数族

#include 

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

sprintf会自动加上空字符,不算在返回值中。

#include 
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
#include 

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

#include 

int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);
									成功返回输入项数,输入出错或文件结束返回EOF

12.细节

#include 
int fileno(FILE *stream);

获取与该流相关的文件描述符。

标准IO库的不足之一:fgets和fputs复制两次数据。内核到标准IO缓冲区之间复制一次(调用write和read),标准IO缓冲区和用户行缓冲区之间复制一次。

习题部分

5.2:fgets 缓冲区满或遇到换行符时停止,会留最后一个位置放置空字符。

fputs正常输出。(空字符不写出)只不过调用次数都会增加。

5.3:表示输出0个字符

5.5:先fflush再fsync

fsync的意义:write操作只会更新内存中的页缓存,硬盘内容不会立即更新。需要等待一段时间或者内存中脏页面的比例到达一定程度时,内核才会将脏页面同步到硬盘上(放入设备的IO请求队列)。

那么就有了一个时间间隔 : write结束和内核将脏页面同步到硬盘前。这个间隔系统因某种原因崩溃,那么我们的脏页面都丢失了。对于需要保持事务持久化和一致性的数据库程序来说,write的异步操作是不能满足的。所以需要同步IO原语来保证及时将脏页面写入硬盘。