學習標准輸入輸出,我們都會遇到一個概念,流和緩沖區,但到底什么是流,什么是緩沖區呢?
書《C Primer Plus》上說,C程序處理一個流而不是直接處理文件。后面的解釋十分抽象:『流(stream)是一個理想化的數據流,實際輸入或輸出映射到這個數據流』。這個流具體是一個怎么樣的東西呢?
流這個定義非常的形象。我們可以這樣理解:
你聲明一個FILE *fp ,並把fopen(某個文件)返回的值賦予fp這兩個動作就相當於建立了一個水龍頭,當你用getc(fp)之類的輸入函數讀取文件字符時就相當於擰開了水龍頭,每讀取一個字符,這個文件就像水一樣的流動一下,fp所指的地址自然就向后移動了一位。
1 int ch; 2 3 while((ch=getc(fp))!=EOF) 4 5 putchar(ch);
你看這個循環,可以讀取一個文件的所有字符。如果不是流的話,ch永遠是第一個字符,不會更新。也可以理解為,fp自動++(一個字符的大小)。
但流的概念意味着什么呢?
--流是獨立於設備之外而操縱外設一種邏輯手段。
--大多數外設都是互異的,所以(操縱)它們需要專門的編程技術。
--流對程序員隱藏這些不同點,而准許他們以同樣的方式來處理大多數外設。
--考慮到一連串的字符需要一次讀一個,流(相當於)是具有緩沖作用的接口。
--個人計算機都是基於流架構的。
各大權威對流的說法有些不一致,我認為流既是數據的源或目的地的抽象,也是源和目的地之間流動信息的表示。但流起碼都暗含以下的幾個方面:
1、流是一個抽象的概念,是對信息的一種表達;在程序中,流就是對某個對象輸入輸出信息的抽象。就像運輸工具是對一切運動載體的抽象一樣。
2、流是一種“動”的概念,靜止存儲在介質上的信息只有當它按一定的序列准備“運動”時才稱為流。“從程序移進或移出字節”就是“動”的表現。靜止的信息具有流的潛力,但不一定是流,就像沒有汽油不能行走的汽車一樣,它具有運輸工具的潛力,但它還不是運輸工具(因為它很有可能被當作房子來用了,我就在大街上看見有精明的商人用火車車廂來做酒吧)。
3、流有源頭也有目的地;程序中各種移動的信息都有其源和目的,記得編程(特別是匯編)時,老是要確定好某個操作的源操作數和目的操作數。借用佛教一言也即是:“萬物皆有因果”,這也就像長江一樣,西自唐古拉,而東去太平洋。在高速公路上飛跑的汽車,它必有其出發地和目的地。
4、流一定帶有某種信息,沒有任何內容的流帶着自身來表達“空”信息。就像運輸工具一樣,它不運貨的時候就運着自己這一身的零件(包括駕駛員)並把一樣東西運到目的地,那就是它自己和一個“跑空車”的信息。流有最小的信息單元就是二進制位,含有最小的信息包就是字節,C標准庫提供兩種類型的流:二進制流(binary stream)和文本流(text stream)。二進制流是有未經處理的字節構成的序列;文本流是由文本行組成的序列。而在著名的UNIX系統中,文本流和二進制流是相同的(identical)。
5、流有源頭也有目的地,那么它必定與源頭和目的地相關聯。但人們操作流的時候,最關心的還是其目的地,也就是一個定向(orientation)的意思,就像司機運貨一樣,它首要關心的問題是目的地,而非起點(操作者都知道)。在C語言中,通過打開流來關聯流及其目的地,使用的函數是fopen(),該函數返回一個指向文件的指針(FILE *),該指針包含了足夠的可以控制流准確地到達目的地的信息。
FILE是一個結構體(摘自TC2.0中stdio.h文件)
1 /* Definition of the control structure for streams*/ 2 typedef struct { 3 short level; /* fill/empty level of buffer */ 4 unsigned flags; /* File status flags */ 5 char fd; /* File descriptor */ 6 unsigned char hold; /* Ungetc char if no buffer */ 7 short bsize; /* Buffer size */ 8 unsigned char *buffer; /* Data transfer buffer */ 9 unsigned char *curp; /* Current active pointer */ 10 unsigned istemp; /* Temporary file indicator */ 11 short token; /* Used for validity checking */ 12 } FILE; /* This is the FILE object */
將它稱為流控制結構體(control structure for streams)真好表現出其功能來。舉個例子就好像一卡車司機要把貨物運到X公司,公司主管就會給他一張地圖及X公司的基本信息,這些材料所提供的信息如果足夠的話,那么它就能指導着司機准確地將貨物送達了。C中FILE這個結構體所起的作用就好像是運輸公司把一切有用的指導信息封裝起來的檔案袋一樣。而已有關聯的流要終止這種關聯,就必須關閉流,使用的函數是fclose(),就像運貨公司若不再給X公司運貨了,那么他們就必須要終止合作協議了。
這里要注意的是:C語言中stdin、stdout、stderr分別是標准輸入流、標准輸出流及標准出錯流的邏輯目的,他們都默認對應相應的物理終端。在程序運行伊始,不需要進行open()操作,流自動打開。
那緩沖區又是什么意思呢?
緩沖區(Buffer):
為了匹配計算機快速設備和慢速設備間的通信步伐,計算機中大量使用硬件緩沖區(如CPU中的Cache,內存相對於硬盤和CPU),流是傳輸信息的一種邏輯表示,對流的各種不同操作也可能存在使用緩沖的需求。但是這里的buffer只是一種邏輯概念,不是物理設備。緩沖區存在於流與具體的設備終端或者存儲介質上的文件之間。就好像運貨到一個公司里一樣,合同上的要求是運到X公司,但是實際上是真的把貨物運到X公司的總部大樓嗎?不是。應該是運到X公司的倉庫中。這里的倉庫就有點像我們所說的緩沖區了。也可以這么說,流運動到目的,先經過的是緩存區。
以scanf() printf()為例:
• 緩沖區(流)負責在輸入/輸出設備和程序之間建立聯系。
–輸入設備->內存緩沖區(stdin)->程序
–程序->內存緩沖區(stdout)->輸出設備
• 是一塊臨時的存儲區域,或在內存中,或在設備的控制卡上
. 緩沖類型。
標准庫提供緩沖是為了減少對read和write的調用。提供的緩沖有三種類型(整理自APUE):
- 全緩沖。
在這種情況下,實際的I/O操作只有在緩沖區被填滿了之后才會進行。對駐留在磁盤上的文件的操作一般是有標准I/O庫提供全緩沖。緩沖區一般是在第一次對流進行I/O操作時,由標准I/O函數調用malloc函數分配得到的。
術語flush描述了標准I/O緩沖的寫操作。緩沖區可以由標准I/O函數自動flush(例如緩沖區滿的時候);或者我們對流調用fflush函數。
- 行緩沖
在這種情況下,只有在輸入/輸出中遇到換行符的時候,才會執行實際的I/O操作。這允許我們一次寫一個字符,但是只有在寫完一行之后才做I/O操作。一般的,涉及到終端的流--例如標注輸入(stdin)和標准輸出(stdout)--是行緩沖的。
- 無緩沖
標准I/O庫不緩存字符。需要注意的是,標准庫不緩存並不意味着操作系統或者設備驅動不緩存。
當然,我們常用的scanf() 與 printf() 屬於行緩沖,下面我們來看個例子,可以幫助我們理解緩沖區在標准輸入輸出中的作用:
1 #include <stdio.h> 2 3 int main() 4 { 5 printf("hello world"); 6 while(1); 7 }
我們看看輸出結果:
1 fs@ubuntu:~/qiang/char1$ gcc -o 1 1.c 2 3 fs@ubuntu:~/qiang/char1$ ./1
打出是個空的,為什么呢?
我們上面提到標准輸入輸出是行緩沖,即一行滿了才會刷新,那什么是刷新呢?刷新就是將數據從緩沖區取出來,真正能刷新,要滿足什么條件呢?
1、滿刷新,即一行滿了(1024個字節)才會刷新;
2、遇到'\n'會刷新;
3、調用fflush()函數;
4、程序結束 fclose();
我們可以看到上面的程序,應為有while(1),程序一直沒有結束,沒有'\n',沒有滿行,沒有fflush(),所以並不會輸出;
這樣理解的話,我們可以改動一下了,就寫一個吧,加'\n':
1 #include <stdio.h> 2 3 int main() 4 { 5 printf("helloworld\n"); 6 while(1); 7 }
執行結果如下:
1 fs@ubuntu:~/qiang/char1$ gcc -o 1 1.c 2 fs@ubuntu:~/qiang/char1$ ./1 3 helloworld 4