[ 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的调用次数,从而提高效率。
-
全缓冲,当缓冲区填满后才进行实际IO操作。对于驻留磁盘上的文件通常使用全缓冲。缓冲区的获取一般通过malloc获得。
flush是标准IO缓冲区的写操作。缓冲区可以由标准IO自动冲洗,也可调用fflush冲洗。
例如fflush可以将缓冲区内容写到磁盘上。(还记得fflush(stdout)么,可以将缓冲区内容冲到屏幕上。
-
行缓冲。在这种情况下遇到换行符标准IO库就执行IO操作。抑或当缓冲区满的时候也进行IO操作。
-
不带缓冲。
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中进行选择,对其进行读写操作。
- 每次一个字符IO,一次读或写一个字符。
- 每次一行IO,每次读或写一行,则使用fgets和fputs,以换行符终止。
- 直接IO,fread和fwrite,二进制IO。每次IO读或写某种数量的对象,而对象具有指定长度。
输入函数
#include
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
单个字符读取。成功返回下一个字符,若已到达文件末端或出错返回EOF。
getchar等于getc(stdin)。getc可以用宏实现,fgetc不行。
- getc参数不能为有副作用的表达式,因为可能被计算多次。
- fgetc肯定是一个函数,所以可以得到地址,这就允许将fgetc的地址作为参数传递给另一个函数。
- 调用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效率

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原语来保证及时将脏页面写入硬盘。