標(biāo)準(zhǔn)IO函數(shù)庫(kù)隱藏了buffer大小和分配的細(xì)節(jié),使得我們可以不用關(guān)心預(yù)分配的內(nèi)存大小是否正確的問題。
雖然這使得這個(gè)函數(shù)庫(kù)很容易用,但是如果我們對(duì)函數(shù)的原理不熟悉的話,也容易遇到很多問題。
?
1 流和FILE實(shí)體(Streams and FILE Objects)前面的章節(jié)中,IO集中在文件描述符,每一個(gè)打開的文件都對(duì)應(yīng)一個(gè)文件描述符,通過文件描述符對(duì)文件進(jìn)行操作。
現(xiàn)在使用了標(biāo)準(zhǔn)IO庫(kù),討論的重點(diǎn)集中在流(streams)。
簡(jiǎn)要了解一下流:
只有兩個(gè)函數(shù)可以修改流的orientation:
fwide函數(shù)聲明:
#include <stdio.h>
#include <wchar.h>
int fwide(FILE* fp, int mode);
函數(shù)返回值:
mode取值的不同決定函數(shù)fwide的不同的行為:
當(dāng)我們打開一個(gè)流,函數(shù)fopen返回一個(gè)指向FILE對(duì)象的指針。FILE對(duì)象通常是一個(gè)結(jié)構(gòu)體,包含所有控制流所需要的信息,包括:
?
2 緩存(Buffering)緩存(buffering)的作用是為了盡可能少地調(diào)用read和write系統(tǒng)調(diào)用。
標(biāo)準(zhǔn)IO庫(kù)提供三種類型的buffering:
完全緩存(Fully buffered):在這種緩存機(jī)制中,實(shí)際的IO操作發(fā)生在緩存被寫滿時(shí)。正在寫入硬盤的文件被完全緩存在buffer中。緩存空間往往在第一次IO操作時(shí)通過調(diào)用malloc函數(shù)獲取;
行緩存(Line buffered):在這種緩存機(jī)制中,實(shí)際的IO操作發(fā)生在新的一行字符被讀入或者輸出時(shí),所以允許每一次只輸出一個(gè)字符。行緩存有兩點(diǎn)需要注意:buffer的大小是固定的,所以即使當(dāng)前行沒有讀入或輸出結(jié)束,依然可能發(fā)生實(shí)際的IO,當(dāng)buffer被寫滿時(shí);一旦有輸入(從無緩存流或者行緩存流中輸入)發(fā)生,所以已在buffer中緩存的輸出流都會(huì)被立刻輸出(flush)。
flush:標(biāo)準(zhǔn)IO緩存中內(nèi)容立刻寫入硬盤或者輸出。在終端設(shè)備中,flush的作用也可能是丟棄緩存中得數(shù)據(jù)。
無緩存(Unbuffered):不緩存輸入或輸出內(nèi)容。例如,如果我們使用fputs函數(shù)輸出15個(gè)字符,那么我們希望這15個(gè)字符盡可能快地被打印出來。如標(biāo)準(zhǔn)錯(cuò)誤輸出就要求是無緩存輸出。
ISO C標(biāo)準(zhǔn)要求下面的緩存特性:
上面的標(biāo)準(zhǔn)顯然沒有具體說明各種情況,一般來說:
我們可以使用函數(shù)setbuf和setvbuf函數(shù)更改流的緩存機(jī)制。
函數(shù)聲明:
#include <stdio.h>
void setbuf(FILE* restrict fp, char* restrict buf);
int servbuf(FILE *restrict fp, char* restrict buf, int mode, size_t size);
函數(shù)返回值:
這些函數(shù)必須在流打開之后,其他流操作執(zhí)行之前被調(diào)用。
函數(shù)作用:
setbuf可以打開或關(guān)閉緩存,打開緩存時(shí),buf指向一個(gè)大小為BUFSIZ(stdio.h中定義的宏)的buffer,通常打開的時(shí)完全緩存,如果當(dāng)前流關(guān)聯(lián)的是終端設(shè)備,有的系統(tǒng)也會(huì)使用行緩存;
servbuf可以指定打開哪種類型的緩存。mode的參數(shù)可以取如下的值,如果指定為無緩存,則參數(shù)buf和size都會(huì)被忽略。
函數(shù)行為總結(jié)如下表所示:
通常來說,我們應(yīng)該讓系統(tǒng)自己選擇buffer大小并自動(dòng)分配,這樣標(biāo)準(zhǔn)IO庫(kù)會(huì)在關(guān)閉流時(shí)自動(dòng)釋放該內(nèi)存。
?
flush函數(shù)。
函數(shù)聲明:
#include <stdio.h>
int fflush(FILE *fp);
函數(shù)作用:
使得該流的所有緩存中未寫入硬盤的數(shù)據(jù)傳入內(nèi)核中。
一種特殊情況是,如果fp為NULL,fflush會(huì)使得所有緩存的數(shù)據(jù)都被flush。
?
3 打開流(opening a stream)函數(shù)fopen、freopen和fdopen函數(shù)用來打開一個(gè)標(biāo)準(zhǔn)輸入輸出流。
函數(shù)聲明:
#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char* restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);
函數(shù)細(xì)節(jié):
參數(shù)type取值如下表所示,一共有15種取值,有得取值作用相同:
表格說明:
當(dāng)打開一個(gè)流對(duì)文件進(jìn)行讀寫時(shí),有兩個(gè)限制:
六種方式打開一個(gè)流總結(jié)如下表所示:
需要注意的一點(diǎn)是,當(dāng)以w和a模式創(chuàng)建一個(gè)新文件時(shí),并不能像open或create函數(shù)一樣指定文件的權(quán)限標(biāo)志位。
一種解決方法是通過調(diào)整我們的umask。
打開的流默認(rèn)的是完全緩存,如果該流關(guān)聯(lián)的是終端設(shè)備,則是行緩存。
像之前提到的那樣,我們打開了一個(gè)流,并在其他操作之前,可以調(diào)用setbuf或setvbuf函數(shù)修改緩存方式。
關(guān)閉流
函數(shù)聲明:
#include <stdio.h>
int fclose(FILE* fp);
函數(shù)細(xì)節(jié),關(guān)閉流之前:
當(dāng)我們打開一個(gè)流,我們有三種讀寫方式可供選擇:
函數(shù)聲明:
#include <stdio.h>
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
函數(shù)返回值:
函數(shù)細(xì)節(jié):
?
函數(shù)聲明:
#include <stdio.h>
int ferror(FILE* fp);
int feof(FILE* fp); ? ?// Both return: nonzero(true) if condition is true, 0(false) otherwise
void clearerr(FILE* fp);
在大多的實(shí)現(xiàn)中,F(xiàn)ILE對(duì)象中會(huì)維護(hù)兩個(gè)flag:
這兩個(gè)flag都可以通過調(diào)用clearerr清空。
?
讀取一個(gè)流后,我們可以調(diào)用函數(shù)ungetc壓回讀出來的字符。
函數(shù)聲明:
#include <stdio.h>
int ungetc(int c, FILE* fp);
函數(shù)返回值:c if OK, EOF on error
函數(shù)細(xì)節(jié):只支持單個(gè)個(gè)字符的壓回。
使用場(chǎng)景:
壓回操作常使用在下面的場(chǎng)景:對(duì)于一個(gè)輸入流,我們需要根據(jù)下一個(gè)字符來判斷該如何處理當(dāng)前的字符。
?
輸出函數(shù)輸出函數(shù)和我們討論過的輸入函數(shù)一一對(duì)應(yīng),不再贅述。
函數(shù)聲明:
#include <stdio.h>
int putc(int c, FILE* fp);
int fputc(int c, FILE* fp);
int putchar(int c);
?
5 逐行輸入輸出操作(Line-at-a-Time IO)函數(shù)fgets和gets提供了逐行輸入功能。
函數(shù)聲明:
#include <stdio.h>
char *fgets(char* restrict buf, int n, FILE* restrict fp);
char *gets(char* buf);
函數(shù)細(xì)節(jié):
函數(shù)fputs和puts提供了逐行輸出的功能。
函數(shù)聲明:
#include <stdio.h>
int fputs(const char* restrict str, FILE* restrict fp);
int puts(const char* str);
函數(shù)細(xì)節(jié):
?
6 標(biāo)準(zhǔn)輸入輸出效率分析比較標(biāo)準(zhǔn):
將一定量的數(shù)據(jù)從標(biāo)準(zhǔn)輸入拷貝到標(biāo)準(zhǔn)輸出,計(jì)算這一過程所需要的
Code:
使用getc和putc的版本:
#include "apue.h"
?
int
main(void)
{
? ? int ? ? c;
?
? ? while ((c = getc(stdin)) != EOF)
? ? ? ? if (putc(c, stdout) == EOF)
? ? ? ? ? ? err_sys("output error");
?
? ? if (ferror(stdin))
? ? ? ? err_sys("input error");
?
? ? exit(0);
}
使用fgets和fputs的版本:
#include "apue.h"
?
int
main(void)
{
? ? char? ? buf[MAXLINE];
?
? ? while (fgets(buf, MAXLINE, stdin) != NULL)
? ? ? ? if (fputs(buf, stdout) == EOF)
? ? ? ? ? ? err_sys("output error");
?
? ? if (ferror(stdin))
? ? ? ? err_sys("input error");
?
? ? exit(0);
}
測(cè)試數(shù)據(jù):95.8M 3百萬行
測(cè)試結(jié)果(和第三章中的數(shù)據(jù)進(jìn)行了對(duì)比,之前跳過了該章節(jié),可以自行查看一下):
結(jié)果說明:
?
?
7 小結(jié)標(biāo)準(zhǔn)IO函數(shù)庫(kù)分為兩篇來介紹,本篇是第一篇,主要介紹了
?
?
參考資料:
《Advanced PRogramming in the UNIX Envinronment 3rd》?
?
新聞熱點(diǎn)
疑難解答
圖片精選