1. 文件和流的關係java
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結構體的定義,以下:spa
TC2.0中: typedef struct { short level; /* fill/empty level of buffer */ unsigned flags; /* File status flags */ char fd; /* File descriptor */ unsigned char hold; /* Ungetc char if no buffer */ short bsize; /* Buffer size */ unsigned char *buffer; /* Data transfer buffer */ unsigned char *curp; /* Current active pointer */ unsigned istemp; /* Temporary file indicator */ short token; /* Used for validity checking */ } FILE; /* This is the FILE object */ VC6.0中: #ifndef _FILE_DEFINED struct _iobuf {
char *_ptr; //文件輸入的下一個位置
int _cnt; //當前緩衝區的相對位置
char *_base; //指基礎位置(便是文件的其始位置)
int _flag; //文件標誌
int _file; //文件的有效性驗證
int _charbuf; //檢查緩衝區情況,若是無緩衝區則不讀取
int _bufsiz; //???這個什麼意思
char *_tmpfname; //臨時文件名操作系統
}; typedef struct _iobuf FILE; #define _FILE_DEFINED #endif
2.2 C語言文件管理的實現.net
C程序用不一樣的FILE結構管理每一個文件。程序員可使用文件,可是不須要知道FILE結構的細節。實際上,FILE結構是間接地操做系統的文件控制塊
(FCB)來實現對文件的操做的,以下圖:3d
上面圖中的_file其實是一個描述符,做爲進入打開文件表索引的整數。
2.3 操做系統文件管理簡介
從2.2中的圖能夠看出,C語言經過FILE結構能夠間接操做文件控制塊(FCB)。爲了加深對這些的理解,這裏科普下操做系統對打開文件的管理。
文件是存放在物理磁盤上的,包括文件控制塊(FCB)和數據塊。文件控制塊一般包括文件權限、日期(建立、讀取、修改)、擁有者、文件大小、數據塊信息。數據塊用來存儲實際的內容。對於打開的文件,操做系統是這樣管理的:
系統維護了兩張表,一張是系統級打開文件表,一張是進程級打開文件表(每一個進程有一個)。
系統級打開文件表複製了文件控制塊的信息等;進程級打開文件表保存了指向系統級文件表的指針及其餘信息。
系統級文件表每一項都保存一個計數器,即該文件打開的次數。咱們初次打開一個文件時,系統首先查看該文件是否已在系統級文件表中,若是不在,則建立該項信息,不然,計數器加1。當咱們關閉一個文件時,相應的計數也會減1,當減到0時,系統將系統級文件表中的項刪除。
進程打開一個文件時,會在進程級文件表中添加一項。每項的信息包括當前文件偏移量(讀寫文件的位置)、存取權限、和一個指向系統級文件表中對應文件項的指針。系統級文件表中的每一項經過文件描述符(一個非負整數)來標識。
聯繫2.2和2.3上面的內容,能夠發現,應該是這樣的:FILE結構體中的_file成員應該是指向進程級打開文件表,而後,經過進程級打開文件表能夠找到系統級打開文件表,進而能夠經過FCB操做物理磁盤上面的文件。
2.4 文件操做的例子
filetest.cpp中的內容以下:
#include<stdio.h> int main() { printf("Hello World!\n"); return 0; }
運行結果以下:
經過這個程序能夠看出,應該是每打開一次文件,哪怕屢次打開的都是同一個文件,進程級打開文件表中應該都會添加一個記錄。若是是打開的是同一個文件,這多條記錄對應着同一個物理磁盤文件。因爲每一次打開文件所進行的操做都是經過進程級打開文件表中不一樣的記錄來實現的,這樣,至關於每次打開文件的操做是相對獨立的,這就是上面的程序的運行結果中,兩次讀取文件的結果是同樣的(而不是第二次讀取從第一次結束的位置進行)。
另外,還能夠看出,程序運行的時候,默認三個流是打開的stdin,stdout和stderr,它們的_file描述符分別是0、1和2。也能夠看出,該程序打開的文件描述符依次從3開始遞增。
3.順序訪問文件
3.1 順序寫入文件
先看一個例子:
#include <stdio.h> int main() { int account;//帳號 char name[30];//帳號名 double balance;//餘額 FILE *cfPtr; if ((cfPtr=fopen("clients.dat","w"))==NULL) { printf("File could not be opened.\n"); } else { printf("Enter the account, name and the balance:\n"); printf("Enter EOF to end input.\n"); printf("? "); scanf("%d%s%lf",&account,name,&balance); while(!feof(stdin)) { fprintf(cfPtr,"%d %s %.2f\n",account,name,balance); printf("? "); scanf("%d%s%lf",&account,name,&balance); } fclose(cfPtr); } return 0; }
運行結果:
從上面的例子中能夠看出,寫入文件大體需兩步:定義文件指針和打開文件。
函數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 建立隨機訪問的文件
#include<stdio.h> struct clientData { int acctNum; char lastName[15]; char firstName[10]; double balance; }; int main() { int i; struct clientData blankClient={0,"","",0.0}; FILE *cfPtr; if ((cfPtr = fopen("credit.dat","wb"))== NULL) { printf("File could not be opened.\n"); } else { for (i=1;i<=100;i++) { fwrite(&blankClient,sizeof(struct clientData),1,cfPtr); } fclose(cfPtr); } return 0; }
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 從隨機訪問文件中讀取數據
運行結果: