1. 檔案和流的關系
C将每個檔案簡單地作為順序位元組流(如下圖)。每個檔案用檔案結束符結束,或者在特定位元組數的地方結束,這個特定的位元組數可以存儲在系統維護的管理資料結構中。當打開檔案時,就建立了和檔案的關系。
在開始執行程式的時候,将自動打開3個檔案和相關的流:标準輸入流、标準輸出流和标準錯誤。流提供了檔案和程式的通信通道。例如,标準輸入流使得程式可以從鍵盤讀取資料,而标準輸出流使得程式可以在螢幕上輸出資料。打開一個檔案将傳回指向FILE結構(在stdio.h中定義)的指針,它包含用于處理檔案的資訊,也就是說,這個結構包含檔案描述符。檔案描述符是作業系統數組(打開檔案清單的索引)。每個數組元素包含一個檔案控制塊(FCB, File Control Block),作業系統用它來管理特定的檔案。
标準輸入、标準輸出和标準錯誤是用檔案指針stdin、stdout和stderr來處理的。

2. C語言檔案操作的底層實作簡介
2.1 FILE結構體
C語言的stdio.h頭檔案中,定義了用于檔案操作的結構體FILE。這樣,我們通過fopen傳回一個檔案指針(指向FILE結構體的指針)來進行檔案操作。可以在stdio.h(位于visual studio安裝目錄下的include檔案夾下)頭檔案中檢視FILE結構體的定義,如下:
char *_ptr; //檔案輸入的下一個位置
int _cnt; //目前緩沖區的相對位置
char *_base; //指基礎位置(即是檔案的其始位置)
int _flag; //檔案标志
int _file; //檔案的有效性驗證
int _charbuf; //檢查緩沖區狀況,如果無緩沖區則不讀取
int _bufsiz; //???這個什麼意思
char *_tmpfname; //臨時檔案名
2.2 C語言檔案管理的實作
C程式用不同的FILE結構管理每個檔案。程式員可以使用檔案,但是不需要知道FILE結構的細節。實際上,FILE結構是間接地作業系統的檔案控制塊
(FCB)來實作對檔案的操作的,如下圖:
上面圖中的_file實際上是一個描述符,作為進入打開檔案表索引的整數。
2.3 作業系統檔案管理簡介
從2.2中的圖可以看出,C語言通過FILE結構可以間接操作檔案控制塊(FCB)。為了加深對這些的了解,這裡科普下作業系統對打開檔案的管理。
檔案是存放在實體磁盤上的,包括檔案控制塊(FCB)和資料塊。檔案控制塊通常包括檔案權限、日期(建立、讀取、修改)、擁有者、檔案大小、資料塊資訊。資料塊用來存儲實際的内容。對于打開的檔案,作業系統是這樣管理的:
1
<code>系統維護了兩張表,一張是系統級打開檔案表,一張是程序級打開檔案表(每個程序有一個)。</code>
系統級打開檔案表複制了檔案控制塊的資訊等;程序級打開檔案表儲存了指向系統級檔案表的指針及其他資訊。
系統級檔案表每一項都儲存一個計數器,即該檔案打開的次數。我們初次打開一個檔案時,系統首先檢視該檔案是否已在系統級檔案表中,如果不在,則建立該項資訊,否則,計數器加1。當我們關閉一個檔案時,相應的計數也會減1,當減到0時,系統将系統級檔案表中的項删除。
程序打開一個檔案時,會在程序級檔案表中添加一項。每項的資訊包括目前檔案偏移量(讀寫檔案的位置)、存取權限、和一個指向系統級檔案表中對應檔案項的指針。系統級檔案表中的每一項通過檔案描述符(一個非負整數)來辨別。
聯系2.2和2.3上面的内容,可以發現,應該是這樣的:FILE結構體中的_file成員應該是指向程序級打開檔案表,然後,通過程序級打開檔案表可以找到系統級打開檔案表,進而可以通過FCB操作實體磁盤上面的檔案。
2.4 檔案操作的例子
filetest.cpp中的内容如下:
運作結果如下:
通過這個程式可以看出,應該是每打開一次檔案,哪怕多次打開的都是同一個檔案,程序級打開檔案表中應該都會添加一個記錄。如果是打開的是同一個檔案,這多條記錄對應着同一個實體磁盤檔案。由于每一次打開檔案所進行的操作都是通過程序級打開檔案表中不同的記錄來實作的,這樣,相當于每次打開檔案的操作是相對獨立的,這就是上面的程式的運作結果中,兩次讀取檔案的結果是一樣的(而不是第二次讀取從第一次結束的位置進行)。
另外,還可以看出,程式運作的時候,預設三個流是打開的stdin,stdout和stderr,它們的_file描述符分别是0、1和2。也可以看出,該程式打開的檔案描述符依次從3開始遞增。
3.順序通路檔案
3.1 順序寫入檔案
先看一個例子:
運作結果:
從上面的例子中可以看出,寫入檔案大緻需兩步:定義檔案指針和打開檔案。
函數fopen有兩個參數:檔案名和檔案打開模式。檔案打開模式‘w’說明檔案時用于寫入的。如果以寫入模式打開的檔案不存在,則fopen将建立該檔案。如果打開現有的檔案來寫入,則将抛棄檔案原有的内容而沒有任何警告。在程式中,if語句用于确定檔案指針cfPtr是否是NULL(沒有成功打開檔案時fopen的傳回值)。如果是NULL,則将輸出錯誤消息,然後程式終止。否則,處理輸入并寫入到檔案中。
foef(stdin)用來确定使用者是否從标準輸入輸入了檔案結束符。檔案結束符通知程式沒有其他資料可以處理了。foef的參數是指向測試是否為檔案結束符的FILE指針。一旦輸入了檔案結束符,函數将傳回一個非零值;否則,函數傳回0。當沒有輸入檔案結束符時,程式繼續執行while循環。
fprintf(cfPtr,"%d %s %.2f\n",account,name,balance);向檔案clients.dat中寫入資料。稍後通過用于讀取檔案的程式,就可以提取資料。函數fprintf和printf等價,隻是fprintf還需要一個指向檔案的指針,所有資料都寫入到這個檔案中。
在使用者輸入檔案結束之後,程式用fclose關閉clients.dat檔案,并結束運作。函數fclose也接收檔案指針作為參數。如果沒有明确地調用函數fclose,則作業系統通常在程式執行結束的稍後關閉檔案。這是作業系統“内務管理”的一個示例,但是,這樣可能會帶來一些難以預料的問題,是以一定要注意在使用結束之後關閉檔案。
3.2 檔案打開模式
模式
說明
r
打開檔案,進行讀取。
w
建立檔案,以進行寫入。如果檔案已經存在,則删除目前内容。
a
追加,打開或建立檔案以在檔案尾部寫入。
r+
打開檔案以進行更新(讀取和寫入)。
w+
建立檔案以進行更新。如果檔案已經存在,則删除目前内容。
a+
追加,打開或者建立檔案以進行更新,在檔案尾部寫入。
3.3 順序讀取檔案
下面的例子讀取的是上一個例子中寫入資料生成的檔案。
上面的例子中,隻需将第一個例子中的檔案打開模式從w變為r,就可以打開檔案讀取資料。
同樣地,fscanf(cfPtr,"%d%s%lf",&account,name,&balance);函數從檔案中讀取一條記錄。函數fscanf和函數scanf等價看,隻是fscanf接收将從中讀取資料的檔案指針作為參數。在第一次執行前面的語句時,account的值為100,name的值是Jones,而balance等于24.98。每次執行第二條fscanf語句時,将從檔案中讀取另一條記錄,而account,name和balance将有新值。當到達檔案結束位置時,關閉檔案,而程式終止。
要從檔案中順序檢索資料,程式通常從檔案的開始來讀取,而且連續讀取所有資料,直至找到期望的資料。在程式執行過程中,有可能會多次處理檔案中的資料(重新從檔案的開頭處理資料)。這時候就要用到函數rewind(cfPtr);,它可以使程式的檔案位置指針(表示檔案中将要讀取或者寫入的下一個位元組的位置)重新設定到檔案的開頭(也就是偏移量為0的位元組)。注意,檔案位置指針并不是指針,它是指定檔案中将進行下一次讀取或者寫入的位置的整數值,有時候也稱其為檔案偏移量,它是FILE結構的成員。
4.随機通路檔案
檔案中用格式化輸入函數fprintf所建立的記錄的長度并不是完全一緻的。然而,在随機通路檔案中,單個記錄的長度通常是固定的,而且可以直接通路(這樣速度更快)而無需通過其他記錄來查找。這使得随機檔案通路适合飛機訂票系統,銀行系統,銷售點系統和其他需要快速通路特定資料的事務處理系統。我們可以有很多方法來實作随機通路檔案,但是這裡我們将把讨論的範圍限制在使用固定長度記錄的簡單方法上。
函數fwrite把從記憶體中特定位置開始的指定數量的位元組寫入到檔案位置指針指定的檔案位置,函數fread從檔案位置指針指定的檔案位置處把指定數量的位元組複制到指定的記憶體位置。fwrite和fread可以從磁盤上讀取資料數組,以及向磁盤上寫入資料數組。fread和fwrite的第三個參數是從磁盤中讀取或者寫入到磁盤上的數組元素的個數。
檔案處理程式很少向檔案中寫入字段。通常情況下,它們一次寫入一個struct。
4.1 建立随機通路的檔案
fwrite(&blankClient,sizeof(struct clientData),1,cfPtr);用于向檔案中寫入一個資料塊,其會在cfPtr指向的檔案中寫入大小為sizeof(struct clientData)的結構blankClient。當然,也可以寫入對象數組的多個元素,隻需把數組名傳給第一個參數,把要寫入的元素個數寫入第三個參數即可。
4.2 随機向随機通路檔案中寫入資料
fseek(cfPtr,(client.acctNum-1)*sizeof(struct clientData),SEEK_SET);将cfPtr所引用檔案的位置指針移動到由(client.acctNum-1)*sizeof(struct clientData)計算所得到的位元組位置處,這個表達式的值稱為偏移量或者位移。負号常量SEEK_SET說明,檔案位置指針指向的位置是相對于檔案開頭的偏移量。
ANSI标準制定了fseek的函數原型為int fseek(FILE *stream, long int offset, int whence);其中offset是stream指向的檔案中從位置whence開始的位元組數。參數whence可以有三個值:SEEK_SET, SEEKCUR或者SEEK_END,分别對應檔案的開頭目前位置和結尾。
4.2 從随機通路檔案中讀取資料
本文轉自二郎三郎部落格園部落格,原文連結:http://www.cnblogs.com/haore147/p/3648395.html,如需轉載請自行聯系原作者