浅谈无缓存I/O操作和标准I/O文件操作区别 (转载)
??? 系統(tǒng)調(diào)用,英文名system call,每個(gè)操作系統(tǒng)都在內(nèi)核里有一些內(nèi)建的函數(shù)庫,這些函數(shù)可以用來完成一些系統(tǒng)系統(tǒng)調(diào)用把應(yīng)用程序的請(qǐng)求傳給內(nèi)核,調(diào)用相應(yīng)的的內(nèi)核函數(shù)完成所需的處理,將處理結(jié)果返回給應(yīng)用程序,如果沒有系統(tǒng)調(diào)用和內(nèi)核函數(shù),用戶將不能編寫大型應(yīng)用程序,及別的功能,這些函數(shù)集合起來就叫做程序接口或應(yīng)用編程接口(Application Programming Interface,API),我們要在這個(gè)系統(tǒng)上編寫各種應(yīng)用程序,就是通過這個(gè)API接口來調(diào)用系統(tǒng)內(nèi)核里面的函數(shù)。如果沒有系統(tǒng)調(diào)用,那么應(yīng)用程序就失去內(nèi)核的支持。
??? 現(xiàn)在,再聊不帶緩存的I/O操作:
???
??? 1:不帶緩存,不是直接對(duì)磁盤文件進(jìn)行讀取操作,像read()和write()函數(shù),它們都屬于系統(tǒng)調(diào)用,只不過在用戶層沒有緩存,所以叫做無緩存IO,但對(duì)于內(nèi)核來說,還是進(jìn)行了緩存,只是用戶層看不到罷了。如果這一點(diǎn)看不懂,請(qǐng)看第二點(diǎn);
??? 2:帶不帶緩存是相對(duì)來說的,如果你要寫入數(shù)據(jù)到文件上時(shí)(就是寫入磁盤上),內(nèi)核先將數(shù)據(jù)寫入到內(nèi)核中所設(shè)的緩沖儲(chǔ)存器,假如這個(gè)緩沖儲(chǔ)存器的長度是100個(gè)字節(jié),你調(diào)用系統(tǒng)函:
??? ssize_t write (int fd,const void * buf,size_t count);
??? 寫操作時(shí),設(shè)每次寫入長度count=10個(gè)字節(jié),那么你幾要調(diào)用10次這個(gè)函數(shù)才能把這個(gè)緩沖區(qū)寫滿,此時(shí)數(shù)據(jù)還是在緩沖區(qū),并沒有寫入到磁盤,緩沖區(qū)滿時(shí)才進(jìn)行實(shí)際上的IO操作,把數(shù)據(jù)寫入到磁盤上,所以上面說的“不帶緩存""不是沒有緩存而是沒有直寫進(jìn)磁盤就是這個(gè)意思
??? 那么,既然不帶緩存的操作實(shí)際在內(nèi)核是有緩存器的,那帶緩存的IO操作又是怎么回事呢?
??? 帶緩存IO也叫標(biāo)準(zhǔn)IO,符合ANSI C 的標(biāo)準(zhǔn)IO處理,不依賴系統(tǒng)內(nèi)核,所以移植性強(qiáng),我們使用標(biāo)準(zhǔn)IO操作很多時(shí)候是為了減少對(duì)read()和write()的系統(tǒng)調(diào)用次數(shù),帶緩存IO其實(shí)就是在用戶層再建立一個(gè)緩存區(qū),這個(gè)緩存區(qū)的分配和優(yōu)化長度等細(xì)節(jié)都是標(biāo)準(zhǔn)IO庫代你處理好了,不用去操心,還是用上面那個(gè)例子說明這個(gè)操作過程:
??? 上面說要寫數(shù)據(jù)到文件上,內(nèi)核緩存(注意這個(gè)不是用戶層緩存區(qū))區(qū)長度是100字節(jié),我們調(diào)用不帶緩存的IO函數(shù)write()就要調(diào)用10次,這樣系統(tǒng)效率低,現(xiàn)在我們?cè)谟脩魧咏⒘硪粋€(gè)緩存區(qū)(用戶層緩存區(qū)或者叫流緩存),假設(shè)流緩存的長度是50字節(jié),我們用標(biāo)準(zhǔn)C庫函數(shù)的fwrite()將數(shù)據(jù)寫入到這個(gè)流緩存區(qū)里面,流緩存區(qū)滿50字節(jié)后在進(jìn)入內(nèi)核緩存區(qū),此時(shí)再調(diào)用系統(tǒng)函數(shù)write()將數(shù)據(jù)寫入到文件(實(shí)質(zhì)是磁盤)上,看到這里,你用該明白一點(diǎn),標(biāo)準(zhǔn)IO操作fwrite()最后還是要掉用無緩存IO操作write,這里進(jìn)行了兩次調(diào)用fwrite()寫100字節(jié)也就是進(jìn)行兩次系統(tǒng)調(diào)用write()。
??? 如果看到這里還沒有一點(diǎn)眉目的話,那就比較麻煩了,希望下面兩條總結(jié)能夠幫上忙:
??? 無緩存IO操作數(shù)據(jù)流向路徑:數(shù)據(jù)——內(nèi)核緩存區(qū)——磁盤
??? 標(biāo)準(zhǔn)IO操作數(shù)據(jù)流向路徑:數(shù)據(jù)——流緩存區(qū)——內(nèi)核緩存區(qū)——磁盤
??? 下面是一個(gè)網(wǎng)友的見解,以供參考:
??? 不帶緩存的I/O對(duì)文件描述符操作,下面帶緩存的I/O是針對(duì)流的。
??? 標(biāo)準(zhǔn)I/O庫就是帶緩存的I/O,它由ANSI C標(biāo)準(zhǔn)說明。當(dāng)然,標(biāo)準(zhǔn)I/O最終都會(huì)調(diào)用上面的I/O例程。標(biāo)準(zhǔn)I/O庫代替用戶處理很多細(xì)節(jié),比如緩存分配、以優(yōu)化長度執(zhí)行I/O等。
??? 標(biāo)準(zhǔn)I/O提供緩存的目的就是減少調(diào)用read和write的次數(shù),它對(duì)每個(gè)I/O流自動(dòng)進(jìn)行緩存管理(標(biāo)準(zhǔn)I/O函數(shù)通常調(diào)用malloc來分配緩存)。它提供了三種類型的緩存:
??? 1) 全緩存。當(dāng)填滿標(biāo)準(zhǔn)I/O緩存后才執(zhí)行I/O操作。磁盤上的文件通常是全緩存的。
??? 2) 行緩存。當(dāng)輸入輸出遇到新行符或緩存滿時(shí),才由標(biāo)準(zhǔn)I/O庫執(zhí)行實(shí)際I/O操作。stdin、stdout通常是行緩存的。
??? 3) 無緩存。相當(dāng)于read、write了。stderr通常是無緩存的,因?yàn)樗仨毐M快輸出。
??? 一般而言,由系統(tǒng)選擇緩存的長度,并自動(dòng)分配。標(biāo)準(zhǔn)I/O庫在關(guān)閉流的時(shí)候自動(dòng)釋放緩存。
??? 在標(biāo)準(zhǔn)I / O庫中,一個(gè)效率不高的不足之處是需要復(fù)制的數(shù)據(jù)量。當(dāng)使用每次一行函數(shù)fgets和fputs時(shí),通常需要復(fù)制兩次數(shù)據(jù):一次是在內(nèi)核和標(biāo)準(zhǔn)I / O緩存之間(當(dāng)調(diào)用read和write時(shí)),第二次是在標(biāo)準(zhǔn)I / O緩存(通常系統(tǒng)分配和管理)和用戶程序中的行緩存(fgets的參數(shù)就需要一個(gè)用戶行緩存指針)之間。
??? 不管上面講的到底懂沒懂,記住一點(diǎn):
??? 使用標(biāo)準(zhǔn)I / O例程的一個(gè)優(yōu)點(diǎn)是無需考慮緩存及最佳I / O長度的選擇,并且它并不比直接調(diào)用read、write慢多少。
??? 帶緩存的文件操作是標(biāo)準(zhǔn)C 庫的實(shí)現(xiàn),第一次調(diào)用帶緩存的文件操作函數(shù)時(shí)標(biāo)準(zhǔn)庫會(huì)自動(dòng)分配內(nèi)存并且讀出一段固定大小的內(nèi)容存儲(chǔ)在緩存中。所以以后每次的讀寫操作并不是針對(duì)硬盤上的文件直接進(jìn)行的,而是針對(duì)內(nèi)存中的緩存的。何時(shí)從硬盤中讀取文件或者向硬盤中寫入文件有標(biāo)準(zhǔn)庫的機(jī)制控制。不帶緩存的文件操作通常都是系統(tǒng)提供的系統(tǒng)調(diào)用,更加低級(jí),直接從硬盤中讀取和寫入文件,由于 IO瓶頸的原因,速度并不如意,而且原子操作需要程序員自己保證,但使用得當(dāng)?shù)脑捫什⒉徊睢A硗鈽?biāo)準(zhǔn)庫中的帶緩存文件IO 是調(diào)用系統(tǒng)提供的不帶緩存IO實(shí)現(xiàn)的。
??? 這里為了說明標(biāo)準(zhǔn)I/O的工作原理,借用了glibc中標(biāo)準(zhǔn)I/O實(shí)現(xiàn)的細(xì)節(jié),所以代碼多是不可移植的.
??? 1. buffered I/O, 即標(biāo)準(zhǔn)I/O
??? 首先,要明確,unbuffered I/O只是相對(duì)于buffered I/O,即標(biāo)準(zhǔn)I/O來說的。而不是說unbuffered I/O讀寫磁盤時(shí)不用緩沖。實(shí)際上,內(nèi)核是存在高速緩沖區(qū)來進(jìn)行。真正的磁盤讀寫的,不過這里要討論的buffer跟內(nèi)核中的緩沖區(qū)無關(guān).
??? buffered I/O的目的是什么呢?很簡單,buffered I/O的目的就是為了提高效率.請(qǐng)明確一個(gè)關(guān)系,那就是:
??? buffered I/O庫函數(shù)(fread, fwrite等,用戶空間) <----call--->?
??? buffered I/O庫函數(shù)都是調(diào)用相關(guān)的unbuffered I/O系統(tǒng)調(diào)用來實(shí)現(xiàn)的,他們并不直接讀寫磁盤.那么,效率的提高從何而來呢?
??? 注意到,buffered I/O中都是庫函數(shù),而unbuffered I/O中為系統(tǒng)調(diào)用,使用庫函數(shù)的效率是高于使用系統(tǒng)調(diào)用的.buffered I/O就是通過盡可能的少使用系統(tǒng)調(diào)用來提高效率的.它的基本方法是,在用戶進(jìn)程空間維護(hù)一塊緩沖區(qū),第一次讀(庫函數(shù))的時(shí)候用read(系統(tǒng)調(diào)用)多從內(nèi)核讀出一些數(shù)據(jù),下次在要讀(庫函數(shù))數(shù)據(jù)的時(shí)候,先從該緩沖區(qū)讀,而不用進(jìn)行再次read(系統(tǒng)調(diào)用)了.同樣,寫的時(shí)候,先將數(shù)據(jù)寫入(庫函數(shù))一個(gè)緩沖區(qū),多次以后,在集中進(jìn)行一次write(系統(tǒng)調(diào)用),寫入內(nèi)核空間.
??? buffered I/O中的fgets, puts, fread, fwrite等和unbufferedI/O中的read,write等就是調(diào)用和被調(diào)用的關(guān)系
??? 下面是一個(gè)利用buffered I/O讀取數(shù)據(jù)的例子:
- #include?<stdlib.h>??
- #include?<stdio.h>??
- #include?<sys/types.h>??
- #include?<sys/stat.h>??
- #include?<fcntl.h>??
- ??
- int?main(void)??
- {??
- ??char?buf[5];??
- ??FILE?*myfile?=?stdin;??
- ??fgets(buf,?5,?myfile);??
- ??fputs(buf,?myfile);??
- ????
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(void)
{char buf[5];FILE *myfile = stdin;fgets(buf, 5, myfile);fputs(buf, myfile);return 0;
}
??? buffered I/O中的"buffer"到底是指什么呢?這個(gè)buffer在什么地方呢?FILE是什么呢?它的空間是怎么分配的呢???? 要弄清楚這些問題,就要看看FILE是如何定義和運(yùn)作的了.(特別說明,在平時(shí)寫程序時(shí),不用也不要關(guān)心FILE是如何定義和運(yùn)作的,最好不要直接操作它,這里使用它,只是為了說明buffered IO)
??? 下面的這個(gè)是glibc給出的FILE的定義,它是實(shí)現(xiàn)相關(guān)的,別的平臺(tái)定義方式不同.
??? [cpp] view plaincopyprint?
- struct?_IO_FILE?{??
- ??int?_flags;???
- ??#define?_IO_file_flags?_flags??
- ??char*?_IO_read_ptr;??
- ??char*?_IO_read_end;??
- ??char*?_IO_read_base;??
- ??char*?_IO_write_base;??
- ??char*?_IO_write_ptr;??
- ??char*?_IO_write_end;??
- ??char*?_IO_buf_base;??
- ??char*?_IO_buf_end;??
- ??char?*_IO_save_base;??
- ??char?*_IO_backup_base;??
- ??char?*_IO_save_end;??
- ??struct?_IO_marker?*_markers;??
- ??struct?_IO_FILE?*_chain;??
- ??int?_fileno;??
- };??
struct _IO_FILE {int _flags; #define _IO_file_flags _flagschar* _IO_read_ptr;char* _IO_read_end;char* _IO_read_base;char* _IO_write_base;char* _IO_write_ptr;char* _IO_write_end;char* _IO_buf_base;char* _IO_buf_end;char *_IO_save_base;char *_IO_backup_base;char *_IO_save_end;struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno;
}; ???
上面的定義中有三組重要的字段:
??? 1.char* _IO_read_ptr;??????? char* _IO_read_end;??? char* _IO_read_base;
??? 2.char* _IO_write_base;?? char* _IO_write_ptr;????? char* _IO_write_end;
??? 3.char* _IO_buf_base;????? char* _IO_buf_end;
??? 其中
??? _IO_read_base 指向"讀緩沖區(qū)"
??? _IO_read_end?
??? _IO_read_end - _IO_read_base "讀緩沖區(qū)"的長度
??? _IO_write_base 指向"寫緩沖區(qū)"
??? _IO_write_end 指向"寫緩沖區(qū)"的末尾
??? _IO_write_end - _IO_write_base "寫緩沖區(qū)"的長度
??? _IO_buf_base?
??? _IO_buf_end?
??? _IO_buf_end - _IO_buf_base "緩沖區(qū)"的長度
??? 上面的定義貌似給出了3個(gè)緩沖區(qū),實(shí)際上上面的_IO_read_base,_IO_write_base, _IO_buf_base都指向了同一個(gè)緩沖區(qū).這個(gè)緩沖區(qū)跟上面程序中的char buf[5];沒有任何關(guān)系.
他們?cè)诘谝淮蝏uffered I/O操作時(shí)由庫函數(shù)自動(dòng)申請(qǐng)空間,最后由相應(yīng)庫函數(shù)負(fù)責(zé)釋放.(再次聲明,這里只是glibc的實(shí)現(xiàn),別的實(shí)現(xiàn)可能會(huì)不同,后面就不再強(qiáng)調(diào)了)
???? 請(qǐng)看下面的程序(這里給的是stdin,行緩沖的例子):
??? [cpp] view plaincopyprint?
- #include?<stdlib.h>??
- #include?<stdio.h>??
- #include?<sys/types.h>??
- #include?<sys/stat.h>??
- #include?<fcntl.h>??
- ??
- int?main(void)??
- {??
- ??char?buf[5];??
- ??FILE?*myfile?=stdin;??
- ??printf("before?reading\n");??
- ??printf("read?buffer?base?%p\n",?myfile->_IO_read_base);??
- ??printf("read?buffer?length?%d\n",?myfile->_IO_read_end?-?myfile->_IO_read_base);??
- ??printf("write?buffer?base?%p\n",?myfile->_IO_write_base);??
- ??printf("write?buffer?length?%d\n",?myfile->_IO_write_end?-?myfile->_IO_write_base);??
- ??printf("buf?buffer?base?%p\n",?myfile->_IO_buf_base);??
- ??printf("buf?buffer?length?%d\n",?myfile->_IO_buf_end?-?myfile->_IO_buf_base);??
- ??printf("\n");??
- ??fgets(buf,?5,?myfile);??
- ??fputs(buf,?myfile);??
- ??printf("\n");??
- ??printf("after?reading\n");??
- ??printf("read?buffer?base?%p\n",?myfile->_IO_read_base);??
- ??printf("read?buffer?length?%d\n",?myfile->_IO_read_end?-?myfile->_IO_read_base);??
- ??printf("write?buffer?base?%p\n",?myfile->_IO_write_base);??
- ??printf("write?buffer?length?%d\n",?myfile->_IO_write_end?-?myfile->_IO_write_base);??
- ??printf("buf?buffer?base?%p\n",?myfile->_IO_buf_base);??
- ??printf("buf?buffer?length?%d\n",?myfile->_IO_buf_end?-?myfile->_IO_buf_base);??
- ??
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(void)
{char buf[5];FILE *myfile =stdin;printf("before reading\n");printf("read buffer base %p\n", myfile->_IO_read_base);printf("read buffer length %d\n", myfile->_IO_read_end - myfile->_IO_read_base);printf("write buffer base %p\n", myfile->_IO_write_base);printf("write buffer length %d\n", myfile->_IO_write_end - myfile->_IO_write_base);printf("buf buffer base %p\n", myfile->_IO_buf_base);printf("buf buffer length %d\n", myfile->_IO_buf_end - myfile->_IO_buf_base);printf("\n");fgets(buf, 5, myfile);fputs(buf, myfile);printf("\n");printf("after reading\n");printf("read buffer base %p\n", myfile->_IO_read_base);printf("read buffer length %d\n", myfile->_IO_read_end - myfile->_IO_read_base);printf("write buffer base %p\n", myfile->_IO_write_base);printf("write buffer length %d\n", myfile->_IO_write_end - myfile->_IO_write_base);printf("buf buffer base %p\n", myfile->_IO_buf_base);printf("buf buffer length %d\n", myfile->_IO_buf_end - myfile->_IO_buf_base);return 0;
} ???
可以看到,在讀操作之前,myfile的緩沖區(qū)是沒有被分配的,在一次讀之后,myfile的緩沖區(qū)才被分配.這個(gè)緩沖區(qū)既不是內(nèi)核中的緩沖區(qū),也不是用戶分配的緩沖區(qū),而是有用戶進(jìn)程空間中的由buffered I/O系統(tǒng)負(fù)責(zé)維護(hù)的緩沖區(qū).(當(dāng)然,用戶可以可以維護(hù)該緩沖區(qū),這里不做討論了)
??? 上面的例子只是說明了buffered I/O緩沖區(qū)的存在,下面從全緩沖,行緩沖和無緩沖3個(gè)方面看一下buffered I/O是如何工作的.
??? 1.1. 全緩沖
??? 下面是APUE上的原話:
??? 全緩沖"在填滿標(biāo)準(zhǔn)I/O緩沖區(qū)后才進(jìn)行實(shí)際的I/O操作.對(duì)于駐留在磁盤上的文件通常是由標(biāo)準(zhǔn)I/O庫實(shí)施全緩沖的"書中這里"實(shí)際的I/O操作"實(shí)際上容易引起誤導(dǎo),這里并不是讀寫磁盤,而應(yīng)該是進(jìn)行read或write的系統(tǒng)調(diào)用
??? 下面兩個(gè)例子會(huì)說明這個(gè)問題
- #include?<stdlib.h>??
- #include?<stdio.h>??
- #include?<sys/types.h>??
- #include?<sys/stat.h>??
- #include?<fcntl.h>??
- ??
- int?main(void)??
- {??
- ??char?buf[5];??
- ??char?*cur;??
- ??FILE?*myfile;??
- ??myfile?=?fopen("bbb.txt",?"r");??
- ??printf("before?reading,?myfile->_IO_read_ptr:?%d\n",?myfile->_IO_read_ptr?-?myfile->_IO_read_base);??
- ??fgets(buf,?5,?myfile);?//僅僅讀4個(gè)字符??
- ??cur?=?myfile->_IO_read_base;??
- ??while?(cur?<?myfile->_IO_read_end)?//實(shí)際上讀滿了這個(gè)緩沖區(qū)??
- ??{??
- ????printf("%c",*cur);??
- ????cur++;??
- ??}??
- ??printf("\nafter?reading,?myfile->_IO_read_ptr:?%d\n",?myfile->_IO_read_ptr?-?myfile->_IO_read_base);??
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(void)
{char buf[5];char *cur;FILE *myfile;myfile = fopen("bbb.txt", "r");printf("before reading, myfile->_IO_read_ptr: %d\n", myfile->_IO_read_ptr - myfile->_IO_read_base);fgets(buf, 5, myfile); //僅僅讀4個(gè)字符cur = myfile->_IO_read_base;while (cur < myfile->_IO_read_end) //實(shí)際上讀滿了這個(gè)緩沖區(qū){printf("%c",*cur);cur++;}printf("\nafter reading, myfile->_IO_read_ptr: %d\n", myfile->_IO_read_ptr - myfile->_IO_read_base);return 0;
} ???
上面提到的bbb.txt文件的內(nèi)容是由很多行的"123456789"組成
??? 上例中,fgets(buf, 5, myfile); 僅僅讀4個(gè)字符,但是,緩沖區(qū)已被寫滿,但是_IO_read_ptr卻向前移動(dòng)了5位,下次再次調(diào)用讀操作時(shí),只要要讀的位數(shù)不超過myfile->_IO_read_end - myfile->_IO_read_ptr那么就不需要再次調(diào)用系統(tǒng)調(diào)用read,只要將數(shù)據(jù)從myfile的緩沖區(qū)拷貝到buf即可(從myfile->_IO_read_ptr開始拷貝)
????
??? 全緩沖讀的時(shí)候:
??? _IO_read_base始終指向緩沖區(qū)的開始
??? _IO_read_end始終指向已從內(nèi)核讀入緩沖區(qū)的字符的下一個(gè)(對(duì)全緩沖來說,buffered I/O讀每次都試圖都將緩沖區(qū)讀滿)
??? _IO_read_ptr始終指向緩沖區(qū)中已被用戶讀走的字符的下一個(gè)
?? (_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時(shí)則已經(jīng)到達(dá)文件末尾,其中_IO_buf_base-_IO_buf_end是緩沖區(qū)的長度
??? 一般大體的工作情景為:
??? 第一次fgets(或其他的)時(shí),標(biāo)準(zhǔn)I/O會(huì)調(diào)用read將緩沖區(qū)充滿,下一次fgets不調(diào)用read而是直接從該緩沖區(qū)中拷貝數(shù)據(jù),直到緩沖區(qū)的中剩余的數(shù)據(jù)不夠時(shí),再次調(diào)用read.在這個(gè)過程中,_IO_read_ptr就是用來記錄緩沖區(qū)中哪些數(shù)據(jù)是已讀的,哪些數(shù)據(jù)是未讀的
[cpp] view plaincopyprint?
- #include?<stdlib.h>??
- #include?<stdio.h>??
- #include?<sys/types.h>??
- #include?<sys/stat.h>??
- #include?<fcntl.h>??
- ??
- int?main(void)??
- {??
- ??char?buf[2048]={0};??
- ??int?i;??
- ??FILE?*myfile;??
- ??myfile?=?fopen("aaa.txt",?"r+");??
- ??i=?0;??
- ??while?(i<2048)??
- ??{??
- ????fwrite(buf+i,?1,?512,?myfile);??
- ????i?+=512;??
- ????//注釋掉這句則可以寫入aaa.txt??
- ????myfile->_IO_write_ptr?=?myfile->_IO_write_base;??
- ????printf("%p?write?buffer?base\n",?myfile->_IO_write_base);??
- ????printf("%p?buf?buffer?base?\n",?myfile->_IO_buf_base);??
- ????printf("%p?read?buffer?base?\n",?myfile->_IO_read_base);??
- ????printf("%p?write?buffer?ptr?\n",?myfile->_IO_write_ptr);??
- ????printf("\n");??
- ??}??
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(void)
{char buf[2048]={0};int i;FILE *myfile;myfile = fopen("aaa.txt", "r+");i= 0;while (i<2048){fwrite(buf+i, 1, 512, myfile);i +=512;//注釋掉這句則可以寫入aaa.txtmyfile->_IO_write_ptr = myfile->_IO_write_base;printf("%p write buffer base\n", myfile->_IO_write_base);printf("%p buf buffer base \n", myfile->_IO_buf_base);printf("%p read buffer base \n", myfile->_IO_read_base);printf("%p write buffer ptr \n", myfile->_IO_write_ptr);printf("\n");}return 0;
} ???
上面這個(gè)是關(guān)于全緩沖寫的例子.??? 全緩沖時(shí),只有當(dāng)標(biāo)準(zhǔn)I/O自動(dòng)flush(比如當(dāng)緩沖區(qū)已滿時(shí))或者手工調(diào)用fflush時(shí),標(biāo)準(zhǔn)I/O才會(huì)調(diào)用一次write系統(tǒng)調(diào)用.例子中,fwrite(buf+i, 1, 512, myfile);這一句只是將buf+i接下來的512個(gè)字節(jié)寫入緩沖區(qū),由于緩沖區(qū)未滿,標(biāo)準(zhǔn)I/O并未調(diào)用write.此時(shí),myfile->_IO_write_ptr = myfile->_IO_write_base;會(huì)導(dǎo)致標(biāo)準(zhǔn)I/O認(rèn)為沒有數(shù)據(jù)寫入緩沖區(qū),所以永遠(yuǎn)不會(huì)調(diào)用write,這樣aaa.txt文件得不到寫入.注釋掉myfile->_IO_write_ptr = myfile->_IO_write_base;前后,看看效果.
??? 全緩沖寫的時(shí)候:
??? _IO_write_base始終指向緩沖區(qū)的開始
??? _IO_write_end全緩沖的時(shí)候,始終指向緩沖區(qū)的最后一個(gè)字符的下一個(gè)
??? (對(duì)全緩沖來說,buffered I/O寫總是試圖在緩沖區(qū)寫滿之后,再系統(tǒng)調(diào)用write)
??? _IO_write_ptr始終指向緩沖區(qū)中已被用戶寫入的字符的下一個(gè)
??? flush的時(shí)候,將_IO_write_base和_IO_write_ptr之間的字符通過系統(tǒng)調(diào)用write寫入內(nèi)核
??? 1.2. 行緩沖
??? 下面是APUE上的原話:
??? 行緩沖"當(dāng)輸入輸出中遇到換行符時(shí),標(biāo)準(zhǔn)I/O庫執(zhí)行I/O操作. "書中這里"執(zhí)行O操作"也容易引起誤導(dǎo),這里不是讀寫磁盤,而應(yīng)該是進(jìn)行read或write的系統(tǒng)調(diào)用
??? 下面兩個(gè)例子會(huì)說明這個(gè)問題
??? 第一個(gè)例子可以用來說明下面這篇帖子的問題
??? http://bbs.chinaunix.net/viewthread.php?tid=954547
- #include?<stdlib.h>??
- #include?<stdio.h>??
- ??
- int?main(void)??
- {??
- ??char?buf[5];??
- ??char?buf2[10];??
- ????
- ??fgets(buf,?5,?stdin);?//第一次輸入時(shí),超過5個(gè)字符??
- ??
- ??puts(stdin->_IO_read_ptr);//本句說明整行會(huì)被一次全部讀入緩沖區(qū),而非僅僅上面需要的個(gè)字符??
- ??stdin->_IO_read_ptr?=?stdin->_IO_read_end;?//標(biāo)準(zhǔn)I/O會(huì)認(rèn)為緩沖區(qū)已空,再次調(diào)用read,注釋掉,再看看效果??
- ??printf("\n");??
- ??puts(buf);??
- ????
- ??fgets(buf2,?10,?stdin);??
- ??puts(buf2);??
- ????
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>int main(void)
{char buf[5];char buf2[10];fgets(buf, 5, stdin); //第一次輸入時(shí),超過5個(gè)字符puts(stdin->_IO_read_ptr);//本句說明整行會(huì)被一次全部讀入緩沖區(qū),而非僅僅上面需要的個(gè)字符stdin->_IO_read_ptr = stdin->_IO_read_end; //標(biāo)準(zhǔn)I/O會(huì)認(rèn)為緩沖區(qū)已空,再次調(diào)用read,注釋掉,再看看效果printf("\n");puts(buf);fgets(buf2, 10, stdin);puts(buf2);return 0;
} ???
上例中, fgets(buf, 5, stdin); 僅僅需要4個(gè)字符,但是,輸入行中的其他數(shù)據(jù)也被寫入緩沖區(qū),但是_IO_read_ptr向前移動(dòng)了5位,下次再次調(diào)用fgets操作時(shí),就不需要再次調(diào)用系統(tǒng)調(diào)用read,只要將數(shù)據(jù)從stdin的緩沖區(qū)拷貝到buf2即可(從stdin->_IO_read_ptr開始拷貝),stdin->_IO_read_ptr = stdin->_IO_read_end;會(huì)導(dǎo)致標(biāo)準(zhǔn)I/O會(huì)認(rèn)為緩沖區(qū)已空,再次fgets則需要再次調(diào)用read.比較一下將該句注釋掉前后的效果
??? 行緩沖讀的時(shí)候,
??? _IO_read_base始終指向緩沖區(qū)的開始
??? _IO_read_end始終指向已從內(nèi)核讀入緩沖區(qū)的字符的下一個(gè)
??? _IO_read_ptr始終指向緩沖區(qū)中已被用戶讀走的字符的下一個(gè)
??? (_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時(shí)則已經(jīng)到達(dá)文件末尾
??? 其中_IO_buf_base-_IO_buf_end是緩沖區(qū)的長度
??? [cpp] view plaincopyprint?
- #include?<stdlib.h>??
- #include?<stdio.h>??
- #include?<sys/types.h>??
- #include?<sys/stat.h>??
- #include?<fcntl.h>??
- ??
- char?buf[5]={'1','2',?'3',?'4',?'5'};?//最后一個(gè)不要是\n,是\n的話,標(biāo)準(zhǔn)I/O會(huì)自動(dòng)flush的,這是行緩沖跟全緩沖的重要區(qū)別??
- ??
- void?writeLog(FILE?*ftmp)??
- {??
- ??fprintf(ftmp,?"%p?write?buffer?base\n",?stdout->_IO_write_base);??
- ??fprintf(ftmp,?"%p?buf?buffer?base?\n",?stdout->_IO_buf_base);??
- ??fprintf(ftmp,?"%p?read?buffer?base?\n",?stdout->_IO_read_base);??
- ??fprintf(ftmp,?"%p?write?buffer?ptr?\n",?stdout->_IO_write_ptr);??
- ??fprintf(ftmp,?"\n");??
- }??
- ??
- int?main(void)??
- {??
- ??int?i;??
- ??FILE?*ftmp;??
- ??ftmp?=?fopen("ccc.txt",?"w");??
- ??i=?0;??
- ??while?(i<4)??
- ??{??
- ????fwrite(buf,?1,?5,?stdout);??
- ????i++;??
- ????*stdout->_IO_write_ptr++?=?'\n';//可以單獨(dú)把這句打開,看看效果??
- ????//getchar();//getchar()會(huì)將標(biāo)準(zhǔn)I/O將緩沖區(qū)輸出??
- ????//打開下面的注釋,你就會(huì)發(fā)現(xiàn)屏幕上什么輸出也沒有??
- ????//stdout->_IO_write_ptr?=?stdout->_IO_write_base;??
- ????writeLog(ftmp);?//這個(gè)只是為了查看緩沖區(qū)指針的變化????
- ??}??
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>char buf[5]={'1','2', '3', '4', '5'}; //最后一個(gè)不要是\n,是\n的話,標(biāo)準(zhǔn)I/O會(huì)自動(dòng)flush的,這是行緩沖跟全緩沖的重要區(qū)別void writeLog(FILE *ftmp)
{fprintf(ftmp, "%p write buffer base\n", stdout->_IO_write_base);fprintf(ftmp, "%p buf buffer base \n", stdout->_IO_buf_base);fprintf(ftmp, "%p read buffer base \n", stdout->_IO_read_base);fprintf(ftmp, "%p write buffer ptr \n", stdout->_IO_write_ptr);fprintf(ftmp, "\n");
}int main(void)
{int i;FILE *ftmp;ftmp = fopen("ccc.txt", "w");i= 0;while (i<4){fwrite(buf, 1, 5, stdout);i++;*stdout->_IO_write_ptr++ = '\n';//可以單獨(dú)把這句打開,看看效果//getchar();//getchar()會(huì)將標(biāo)準(zhǔn)I/O將緩沖區(qū)輸出//打開下面的注釋,你就會(huì)發(fā)現(xiàn)屏幕上什么輸出也沒有//stdout->_IO_write_ptr = stdout->_IO_write_base;writeLog(ftmp); //這個(gè)只是為了查看緩沖區(qū)指針的變化 }return 0;
} ???
這個(gè)例子將將FILE結(jié)構(gòu)中指針的變化寫入的文件ccc.txt,運(yùn)行后可以有興趣的話,可以看看.?? 上面這個(gè)是關(guān)于行緩沖寫的例子.
??? stdout->_IO_write_ptr = stdout->_IO_write_base;會(huì)使得標(biāo)準(zhǔn)I/O認(rèn)為緩沖區(qū)是空的,從而沒有任何輸出.可以將上面程序中的注釋分別去掉,看看運(yùn)行結(jié)果
??? 行緩沖時(shí),下面3個(gè)條件之一會(huì)導(dǎo)致緩沖區(qū)立即被flush
??? 1. 緩沖區(qū)已滿
??? 2. 遇到一個(gè)換行符;比如將上面例子中buf[4]改為'\n'時(shí)
??? 3. 再次要求從內(nèi)核中得到數(shù)據(jù)時(shí);比如上面的程序加上getchar()會(huì)導(dǎo)致馬上輸出
??? 行緩沖寫的時(shí)候:
??? _IO_write_base始終指向緩沖區(qū)的開始
??? _IO_write_end始終指向緩沖區(qū)的開始
??? _IO_write_ptr始終指向緩沖區(qū)中已被用戶寫入的字符的下一個(gè)
??? flush的時(shí)候,將_IO_write_base和_IO_write_ptr之間的字符通過系統(tǒng)調(diào)用write寫入內(nèi)核
??? 1.3. 無緩沖
??? 無緩沖時(shí),標(biāo)準(zhǔn)I/O不對(duì)字符進(jìn)行緩沖存儲(chǔ).典型代表是stderr,這里的無緩沖,并不是指緩沖區(qū)大小為0,其實(shí),還是有緩沖的,大小為1
[cpp] view plaincopyprint?
- #include?<stdlib.h>??
- #include?<stdio.h>??
- #include?<sys/types.h>??
- #include?<sys/stat.h>??
- #include?<fcntl.h>??
- ??
- int?main(void)??
- {??
- ??fputs("stderr",?stderr);??
- ??printf("%d\n",?stderr->_IO_buf_end?-?stderr->_IO_buf_base);??
- ??
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(void)
{fputs("stderr", stderr);printf("%d\n", stderr->_IO_buf_end - stderr->_IO_buf_base);return 0;
} ???
對(duì)無緩沖的流的每次讀寫操作都會(huì)引起系統(tǒng)調(diào)用
??? 1.4 feof的問題
??? CU上已經(jīng)有無數(shù)的帖子在探討feof了,這里從緩沖區(qū)的角度去考察一下.對(duì)于一個(gè)空文件,為什么要先讀一下,才能用feof判斷出該文件到了結(jié)尾了呢?
- #include?<stdlib.h>??
- #include?<stdio.h>??
- #include?<sys/types.h>??
- #include?<sys/stat.h>??
- #include?<fcntl.h>??
- ??
- int?main(void)??
- {??
- ??char?buf[5];??
- ??char?buf2[10];??
- ??
- ??fgets(buf,?sizeof(buf),?stdin);//輸入要于4個(gè),少于13個(gè)字符才能看出效果??
- ??puts(buf);??
- ??
- ??//交替注釋下面兩行??
- ??//stdin->_IO_read_end?=?stdin->_IO_read_ptr+1;??
- ??
- ??stdin->_IO_read_end?=?stdin->_IO_read_ptr?+?sizeof(buf2)-1;??
- ?????
- ??fgets(buf2,?sizeof(buf2),?stdin);??
- ??puts(buf2);??
- ??if?(feof(stdin))??
- ????printf("input?end\n");??
- ??return?0;??
- }??
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(void)
{char buf[5];char buf2[10];fgets(buf, sizeof(buf), stdin);//輸入要于4個(gè),少于13個(gè)字符才能看出效果puts(buf);//交替注釋下面兩行//stdin->_IO_read_end = stdin->_IO_read_ptr+1;stdin->_IO_read_end = stdin->_IO_read_ptr + sizeof(buf2)-1;fgets(buf2, sizeof(buf2), stdin);puts(buf2);if (feof(stdin))printf("input end\n");return 0;
} ???
運(yùn)行上面的程序,輸入多于4個(gè),少于13個(gè)字符,并且以連按兩次ctrl+d為結(jié)束(不要按回車)從上面的例子,可以看出,每當(dāng)滿足(_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時(shí),標(biāo)準(zhǔn)I/O則認(rèn)為已經(jīng)到達(dá)文件末尾,feof(stdin)才會(huì)被設(shè)置,其中_IO_buf_base-_IO_buf_end是緩沖區(qū)的長度.也就是說,標(biāo)準(zhǔn)I/O是通過它的緩沖區(qū)來判斷流是否要結(jié)束了的.這就解釋了為什么即使是一個(gè)空文件,標(biāo)準(zhǔn)I/O也需要讀一次,才能使用feof判斷釋放為空
??? 1.5. 其他說明
??? 很多新手有一個(gè)誤解,就是fgets, fputs代表行緩沖,fread, fwrite代表全緩沖 fgetc, fputc代表無緩沖等等.其實(shí)不是這樣的,是什么樣的緩沖跟使用那個(gè)函數(shù)沒有關(guān)系,而跟你讀寫什么類型的文件有關(guān)系.上面的例子中多次在全緩沖中使用fgets, fputs,而在行緩沖中使用fread, fwrite
??? 下面的是引至APUE的,實(shí)際上ISO C要求:
??? 1.當(dāng)且僅當(dāng)標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出并不涉及交互式設(shè)備時(shí),他們才是全緩沖的
??? 2.標(biāo)準(zhǔn)錯(cuò)誤輸出決不是全緩沖的.
??? 很多系統(tǒng)默認(rèn)使用下列類型的標(biāo)準(zhǔn):
??? 1.標(biāo)準(zhǔn)錯(cuò)誤輸出是不帶緩沖的.
??? 2.如若是涉及終端設(shè)備的其他流,則他們是行緩沖的;否則是全緩沖的.
轉(zhuǎn)自:http://blog.csdn.net/cowbane/article/details/6630298
?
原文:
淺談無緩存I/O操作和標(biāo)準(zhǔn)I/O文件操作區(qū)別
?
?
總結(jié)
以上是生活随笔為你收集整理的浅谈无缓存I/O操作和标准I/O文件操作区别 (转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nginx内存池--pool代码抽取(链
- 下一篇: 利用TCMalloc替换Nginx和Re