天天看點

一個例子叫你了解緩沖輸入與非緩沖輸入,以及流的概念:論read與fget open與fopen的差別一、緩沖輸入與非緩沖輸入。

首先講一下基本概念

本文假設你已經基本了解标準IO庫函數fopen fgets setbuf等和非緩沖IO庫函數open write read lseek等。這裡隻就他們的差別加以闡述,并以最簡單的程式實作他們之間差異的展現。讓你了解流,緩沖,非緩沖的概念。

一、緩沖輸入與非緩沖輸入。

fopen,fgets等屬于緩沖輸入,open,write,read系列屬于非緩沖。

APUE上的解釋:

unbuffered IO是指在每個read和write都調用核心中的一個系統調用,他們不是ISO C的組成部分,但屬于POSIX.1

标準IO庫相容各種系統,是帶緩沖的輸入輸出。屬于進階庫。由ISO C标準定義,在linux上還做了一點擴充,實作了部分隻支援linux的接口 比如 fdopen。

從網上得到的一些值得參考的解釋:

       1. 緩沖檔案系統

       緩沖檔案系統的特點是:在記憶體開辟一個“緩沖區”,為程式中的每一個檔案使用,當執行讀檔案的操作時,從磁盤檔案将資料先讀入記憶體“緩沖區”, 裝滿後再從記憶體“緩沖區”依此讀入接收的變量。執行寫檔案的操作時,先将資料寫入記憶體“緩沖區”,待記憶體“緩沖區”裝滿後再寫入檔案。由此可以看出,記憶體 “緩沖區”的大小,影響着實際操作外存的次數,記憶體“緩沖區”越大,則操作外存的次數就少,執行速度就快、效率高。一般來說,檔案“緩沖區”的大小随機器 而定。

     非緩沖檔案系統

緩沖檔案系統是借助檔案結構體指針來對檔案進行管理,通過檔案指針來對檔案進行通路,既可以讀寫字元、字元串、格式化資料,也可以讀寫二進制數 據。非緩沖檔案系統依賴于作業系統,通過作業系統的功能對檔案進行讀寫,是系統級的輸入輸出,它不設檔案結構體指針,隻能讀寫二進制檔案,但效率高、速度 快,由于ANSI标準不再包括非緩沖檔案系統,是以建議大家最好不要選擇它。open, close, read, write, getc, getchar, putc, putchar 等。

 (這是一個檔案系統層面的緩沖概念)

2. open傳回一個檔案描述符,檔案描述符是linux下的一個概念,linux下的一切裝置都是以檔案的形式操作.如網絡套接字、硬體裝置等。當然包括操作檔案。

fopen是标準c函數。傳回檔案流而不是linux下檔案句柄。

3. 裝置檔案不可以當成流式檔案來用,隻能用open

fopen是用來操縱正規檔案的,并且設有緩沖的,跟open還是有一些差別

一般用fopen打開普通檔案,用open打開裝置檔案

fopen是标準c裡的,而open是linux的系統調用.

他們的層次不同.

fopen可移植,open不能。

4. fopen和open最主要的差別是fopen在使用者态下就有了緩存,在進行read和write的時候減少了使用者态和核心态的切換,而open則每次都需要進行核心态和使用者态的切換;表現為,如果順序通路檔案,fopen系列的函數要比直接調用open系列快;如果随機通路檔案open要比fopen快。

這一回答将fopen 與open的差別解釋的非常到位

5.差別總結

open,fopen

前者屬于低級IO,後者是進階IO。

前者傳回一個檔案描述符(使用者程式區的),後者傳回一個檔案指針。

前者無緩沖,後者有緩沖。

前者與 read, write 等配合使用, 後者與 fread, fwrite等配合使用。

後者是在前者的基礎上擴充而來的,在大多數情況下,用後者。

在linux下fopen函數系列的實作是對open系統調用系列的封裝。

     以鍵盤輸入為例,過程是這樣的:  

    鍵盤中斷 -> driver與硬體互動擷取鍵值 -> driver将鍵值上報給kernel -> kernel 通過緩沖隊列的形式(linux下應該是input子系統)管理得到的消息,并上報給應用層 ->  系統調用 讀取到鍵值,存入檔案或者顯示到螢幕上。

二、fgets,read

由于fgets函數比較個性,還是要稍微介紹一下他的特殊之處:char *fgets(char *buf, int bufsize, FILE *stream);

1、他是操作檔案流的函數 *FILE stream

2、每次最多能讀取bufsize - 1個字元,然後在最後還會加上'\0'

3、每次讀取最多一行,且換行符'\n'會被存入buff中,也就是說讀取一行時 buffsize大于strlen + 2 (‘\n’‘\0’)

read函數很單純哒,我給一個原型就好啦,剩下的自己去看manual吧

int read(int handle, void *buf, int nbyte);

1、操作檔案描述符fd的函數

三、fflush,rewind,setbuf,lseek

前三個是操作流的,lseek是操作檔案描述符的。

代碼分析:(需要加載的頭檔案我都不寫了,自己添加)

代碼1:

int main (void)

{

char buff[10];

read(0,buff,5);//标準輸入的檔案描述符為0

write(1,buff,5);//  标準輸出的檔案描述符為1

exit(0);

}

輸入: 12345678回車  //輸入超過了read的size

輸出:12345

[email protected]:~$678

678: command not found

解釋:首先write是正常工作了的,buff記憶體儲的是{1,2,3,4,5後面是空的}

但是在标準輸入中還緩存着 678\n

程式結束後,緩存會被清空(沖洗 flush)678\n這幾個字元就會在程式結束時強制輸出到terminal中也就成了你輸入了一個指令678,但這并不是指令。

改進代碼1

int main (void)

{

char buff[10];

read(0,buff,5);//鍵盤輸入的檔案描述符為0

write(1,buff,5);//  顯示屏輸出的檔案描述符為1

//fflush(stdin);

//rewind(stdin);

//setbuf(stdin,NULL);

char a = getchar();

putchar(a);

//read(0,buff,5); //特别注意如果這裡再加上一個read 那麼,在第一次輸入後程式不會結束,會阻塞直到再次輸入,這是因為标準輸入流已經把kernal那裡的東西全都讀到ISO C層的buff内了,是以read這種不能從ISO層buff得到資料的非緩沖函數就無法獲得資料了,隻有阻塞直到新的資料輸入。把這個小程式熟悉一下,你基本就明白流和檔案描述符,緩沖與非緩沖輸入的差別了。

exit(0);

}

輸入: 12345678回車  //輸入超過了read的size

輸出:[email protected]:

分析被注釋掉的fflush,rewind,setbuf都是可以清空緩存的函數,但如果此處使用,不會有任何作用,因為這幾個函數是針對檔案流的緩存,即建構在系統調用層之上的緩存,目的是提前預加載檔案減少系統調用次數,核心實作的鍵盤輸出緩存并不在他們可操作範圍之内,read是沒有緩存的,他是直接的系統調用。而getchar是有緩存的函數,調用getchar後ISO C層會以檔案流的形式打開打開标準輸入檔案,接收标準輸入傳來的資料,存入檔案流的緩存中,是以這時标準輸入那邊的緩存就空了。而ISO層的緩存是系統調用層之上的緩存,是以在程式結束時釋放緩存是不會在終端列印字元的。當然如果是以清除緩存為目的顯然getchar沒有達到目的,應該寫為while((a = getchar() != EOF) && a != '\n')

改進代碼3:

了解了緩沖輸入與非緩沖輸入的差別後我們知道還可以把read改寫成fgets這樣一來setbuf就可以清除緩存了(當然這時不清除緩存也不會出現将多餘字元輸出到指令行中的情況了),但是fflush和rewind還是不能用,為什麼呢?因為這裡是linux。。。。如果是在windows下是可以的。這隻是标準實作時的差別,不要在意這些細節。注意sefbuf(stdin,NULL)并不是清空緩存這麼簡單而是将stdin設定為無緩存模式。。。是以雖然有清除緩存的功能,但是其實并不是清除緩存函數。

int main (void)

{

char buff[10];

fgets(buff,5,stdin);//鍵盤輸入的檔案描述符為0

write(1,buff,5);//  顯示屏輸出的檔案描述符為1

//fflush(stdin);

//rewind(stdin);

setbuf(stdin,NULL);

exit(0);

}

輸入: 12345678回車  //輸入超過了read的size

輸出:[email protected]:

繼續閱讀