杭州市建設網(wǎng)站個人免費網(wǎng)站創(chuàng)建入口
4. 標準 IO 庫
- 1. 標準 IO 簡介
- 2. FILE 指針
- 3. 標準輸入、標準輸出和標準錯誤
- 4. fopen() 和 flose()
- 5. fread() 和 fwrite()
- 6. fseek 定位
- 7. 檢查或復位狀態(tài)
- 7.1 feof()
- 7.2 ferrof()
- 7.3 clearerr()
- 8. 格式化 IO
- 8.1 格式化輸出
- 8. 2 格式化輸入
- 9. IO 緩沖
- 9.1 文件 IO 的內核緩沖
- 9.2 刷新文件 IO 的內核緩沖區(qū)
- 9.2.1 控制文件 IO 內核緩沖的系統(tǒng)調用
- 9.2.1 控制文件 IO 內核緩沖的標志
- 9.3 直接 IO,繞過內核緩沖
- 9.3.1 直接 IO 的對齊限制
- 9.3.2 直接 IO 與普通 IO 對比
- 9.4 stdio 緩沖
- 9.4.1 設置 stdio 緩沖
- 9.4.1.1 setvbuf()
- 9.4.1.2 setbuf()
- 9.4.1.3 setbuffer()
- 9.4.2 緩沖模式
- 9.4.3 刷新 stdio 緩沖區(qū)
- 10. 文件描述符和FILE指針互轉
1. 標準 IO 簡介
標準 IO 庫指的式標準 C 庫中用于文件 IO 操作相關的一系列庫函數(shù)的集合,底層是用系統(tǒng) IO 實現(xiàn)的。但是標準 IO 比系統(tǒng) IO 具有更好的可移植性,因為不同的操作系統(tǒng)內核提供的系統(tǒng)調用都是不一樣的;其次標準 IO 具有更高的效率,因為標準 IO 提供了自己的緩沖區(qū),但是系統(tǒng) IO 不具備緩沖區(qū)。
2. FILE 指針
FILE 指針就類似于系統(tǒng) IO 的文件描述符,FILE 指針是一個結構體類型,包含了標準 IO 為管理文件所需要的所有信息,包括文件描述符、指向文件緩沖區(qū)的指針、緩沖區(qū)的長度,當前緩沖區(qū)中的字節(jié)數(shù)以及出錯標志等。該類型定義在stdio.h
中
3. 標準輸入、標準輸出和標準錯誤
標準輸入和標準輸出通常指的就是鍵盤和顯示器,標準錯誤也是通過顯示器顯示的。
通過標準輸入設備與系統(tǒng)進行交互時,進程將從標準輸入 (stdin) 文件中獲取數(shù)據(jù),將正常輸出數(shù)據(jù)輸出到標準輸出 (stdout) 文件,將錯誤信息輸出到標準錯誤 (stderr) 文件中。標準輸出文件和標準錯誤文件都對應終端的屏幕,而標準輸入文件則對應于鍵盤。
4. fopen() 和 flose()
#include <stdio.h>
int flose(FILE *stream);// 成功返回0,失敗返回-1
FILE *fopen(const char *path, const cchar *mode);// 成功返回FILE文件指針,失敗返回NULL
/* 參數(shù):* path: 文件路徑,可以是相對路徑,也可以是絕對路徑* mode: 文件權限,是一個字符串* r:只讀* r+:可讀可寫* w:只寫,如果文件存在,就將文件截斷為0,否則就創(chuàng)建文件* w+:可讀可寫方式打開文件,如果存在就截斷為0,否則就創(chuàng)建文件* a:只寫,默認是追加式寫入,如果文件不存在就創(chuàng)建文件* a+:可讀可寫,以追加的方式寫入,如果文件不存在就創(chuàng)建文件* /
// 如果文件不存在就會創(chuàng)建文件,新文件的默認權限是0666
5. fread() 和 fwrite()
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
/* 參數(shù):* ptr:存放讀取到的數(shù)據(jù)或者需要寫入的數(shù)據(jù)的緩沖區(qū)* size:讀取或寫入的數(shù)據(jù)大小的單位,那么總共數(shù)據(jù)的大小是size*nmemb* nmemb:讀取或寫入的數(shù)據(jù)個數(shù)* stream:FILE指針*/
6. fseek 定位
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
// 這里的后兩個參數(shù)和lseek相同,成功返回0,失敗返回-1
long ftell(FILE *stream);
// 獲取當前讀寫位置偏移量
7. 檢查或復位狀態(tài)
fread() 讀取數(shù)據(jù)時,如果返回值小于 nmemb,表示發(fā)生了錯誤或者已經(jīng)讀到了文件末尾,但是不能具體確定是哪種情況,可以通過判斷錯誤標志或 end-of-file 標志來確定具體情況
7.1 feof()
用于測試文件的 end-of-file 標志,如果被設置了,則調用 feof() 函數(shù)將返回一個非零值,如果沒有被設置就返回0.
#include <stdio.h>
int feof(FILE *stream);
// 當文件的讀寫位置到結尾時,end-of-file 會被設置
7.2 ferrof()
用于測試文件的錯誤標志,如果錯誤標注被設置,返回非零值,否則返回 0.
#include <stdio.h>
int ferror(FILE *stream);
7.3 clearerr()
用于清除 end-of-file 或錯誤標志,當調用上面兩個函數(shù)校驗完之后,通常需要清除這些標志,避免下次校驗時使用到的是上一次的值。對于 end-of-file,除了顯示清除外,調用 fseek 會自動清除
#include <stdio.h>
void clearerr(FILE *stream);
8. 格式化 IO
8.1 格式化輸出
#include <stdio.h>
int print(const char *format,...);
int fprintf(FILE *stream, const char *format,...);
int dprintf(int fd, const char *format,...);
int sprintf(char *buf, const char *format,...);
int snprintf(char *buf, size_t size, const char *format,...);
// format:格式化控制字符串,用于指定后續(xù)的參數(shù)如何進行格式轉換
fprintf(stderr,"hello world\n");
fprintf(stderr,"%d\n",5);dprintf(STDERR_FILENO,"hello world\n");
dprintf(STDERR_FILENO,"%d\n",5);char buf[100];
sprintf(buf,"hello world\n");
sprintf(buf,"%d",100);// 將整形轉換為字符串,并且自動在末尾加上一個終止符// 因為sprinf可能會發(fā)生緩沖區(qū)溢出,就引入了snprintf
// size規(guī)定了緩沖區(qū)的大小,如果寫入到緩沖區(qū)的字節(jié)數(shù)大于size,超出的部分就會丟棄
format:
%[flags][width][.precision][length]type
8. 2 格式化輸入
#include <stdio.h>
int scanf(const char *format,...);
int fscanf(FILE *stream, const char *format,...);
int sscanf(const char *str, const char *format,...);
int a,b,c;
scanf("%d%d%d", &a&b&c);
// 使用該函數(shù)時,進程會被阻塞,直到鍵盤有數(shù)據(jù)輸入int a2,b2,c2;
fscanf(stdin,"%d%d%d",&a2,&b2,&c2);char *str="5454 hello";
char buf[10];
int a3;
sscanf(str, "%d%s",&a,buf);
format:
%[*][width][ength]type
或%[m][width][ength]type
如果添加了 *,格式化輸入函數(shù)會按照轉換說明的指示讀取輸入,但是丟棄輸入,意味著不需要對轉換后的結果進行存儲,所以也就不需要提供相應的指針參數(shù)。如果添加了 m,就只能與%s、 %c 以及%[一起使用,調用者無需分配相應的緩沖區(qū)來保存格式轉換后的數(shù)據(jù),原因在于添加了 m,這些格式化輸入函數(shù)內部會自動分配足夠大小的緩沖區(qū),并將緩沖區(qū)的地址值通過與該格式轉換相對應的指針參數(shù)返回出來,該指針參數(shù)應該是指向char*
變量的指針。隨后,當不再需要此緩沖區(qū)時,調用者應調用 free() 函數(shù)來釋放此緩沖區(qū)。
9. IO 緩沖
9.1 文件 IO 的內核緩沖
文件 IO 在進行文件讀寫操作時并不會直接訪問磁盤設備,而是僅僅在用戶空間緩沖區(qū)和內核緩沖區(qū)之間復制數(shù)據(jù),也就是說系統(tǒng)調用和磁盤操作不是同步的。當多個線程同時向文件寫入數(shù)據(jù)時,就會將文件存放在緩沖區(qū)中,然后只進行依次和磁盤的 IO 操作。
9.2 刷新文件 IO 的內核緩沖區(qū)
強制將文件 IO 的內核緩沖區(qū)中緩存的數(shù)據(jù)刷新到磁盤設備中
9.2.1 控制文件 IO 內核緩沖的系統(tǒng)調用
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
void sync(void);// 不是對某個指定的文件數(shù)據(jù)進行更新,而是刷新所有文件 IO 內核緩沖區(qū)
9.2.1 控制文件 IO 內核緩沖的標志
fd=open(filepath,O_WRONLY|O_DSYNC); // 類似在每個write后調用fdatasync函數(shù)
fd=ofen(filepath,O_WRONLY|O_SYNC); // 每個write都會自動將文件內容數(shù)據(jù)和元數(shù)據(jù)刷新
9.3 直接 IO,繞過內核緩沖
fd=open(filepath,O_WRONLY|O_DIRECT);
9.3.1 直接 IO 的對齊限制
- 應用程序中用于存放數(shù)據(jù)的緩沖區(qū),其內存起始地址必須以塊大小的整數(shù)倍進行對齊
- 寫文件時,文件的位置偏移量必須是塊大小的整數(shù)倍
- 寫入到文件的數(shù)據(jù)大小必須是塊大小的整數(shù)倍
確認塊大小指令df -h
查看 Ubuntu 系統(tǒng)的跟文件系統(tǒng)所掛載的磁盤分區(qū),接著sudo tune2fs -l /dev/sda1 | grep "Block size"
9.3.2 直接 IO 與普通 IO 對比
直接 IO 每次都是直接對磁盤發(fā)起操作,而普通方式只是將用戶空間下的數(shù)據(jù)拷貝到文件 IO 內核緩沖區(qū)中。直接 IO 效率、性能低,只有一些特殊場合用到
9.4 stdio 緩沖
標準 IO 效率比 文件 IO 高的根本就是它維護了自己的緩沖區(qū),減少了和磁盤的交互
9.4.1 設置 stdio 緩沖
9.4.1.1 setvbuf()
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
// 如果 buf 不為 NULL,那么buf指向size大小的內存區(qū)域將作為該文件的stdio緩沖區(qū),所以buf應該以動態(tài)分配或靜態(tài)的方式在堆上開辟空間,而不是在棧上的函數(shù)內分配局部變量。如果buf為NULL,那么stdio庫會自動分配一塊空間作為該文件的stdio緩沖區(qū),除非mode配置為非緩沖區(qū)模式
/* mode:指定緩沖區(qū)的緩沖類型* _IONBF:不對 IO 進行緩沖。每個標準IO函數(shù)將立即調用write或read,并且忽略buf和size參數(shù),可以指定為NULL和0,stderr就是這類* _IOLBF:采用行緩沖,遇到換行符才會執(zhí)行文件IO操作。對于輸出流,在輸出一個換行符前將數(shù)據(jù)緩存(除非緩沖區(qū)已經(jīng)被填滿), 當輸出換行符時,再將這一行數(shù)據(jù)通過文件 I/O write()函數(shù)刷入到內核緩沖區(qū)中;對于輸入流, 每次讀取一行數(shù)據(jù)。 對于終端設備默認采用的就是行緩沖模式,譬如標準輸入和標準輸出。* _IOFBF: 采用全緩沖 I/O。 在這種情況下,在填滿 stdio 緩沖區(qū)后才進行文件 I/O 操作(read、 write)。對于輸出流,當 fwrite 寫入文件的數(shù)據(jù)填滿緩沖區(qū)時,才調用 write()將 stdio 緩沖區(qū)中的數(shù)據(jù)刷入內核緩沖區(qū);對于輸入流, 每次讀取 stdio 緩沖區(qū)大小個字節(jié)數(shù)據(jù)。 默認普通磁盤上的常規(guī)文件默認常用這種緩沖模式*/
// size指定緩沖區(qū)大小
當 stdio 緩沖區(qū)中的數(shù)據(jù)被刷入到內核緩沖區(qū)或被讀取之后,這些數(shù)據(jù)就不會存在于緩沖區(qū)中了,數(shù)據(jù)被刷入了內核緩沖區(qū)或被讀走了
9.4.1.2 setbuf()
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
執(zhí)行和 setvbuf 類似的任務
9.4.1.3 setbuffer()
#include <stdio.h>
void setbuffer(FILE *stream, char *buf, size_t size);
和setbuf類似,但是可以指定緩沖的大小
9.4.2 緩沖模式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{// setvbuf(stdout, NULL, _IONBF, 0);// 將stdout設置為無緩沖printf("Hello World!\n");printf("Hello World!");for ( ; ; )sleep(1);
}
上面的代碼只能看到第一個打印信息,第二個看不到,因為第一個是行緩沖,而第二個是全緩沖,只有當程序結束會刷新緩沖區(qū)。如果去掉注釋,那么兩個都可以打印出來。
9.4.3 刷新 stdio 緩沖區(qū)
#include <stdio.h>
int fflush(FILE *stream); // 刷新緩沖區(qū)
int fclose(FILE *stream); // 關閉緩沖區(qū)
強制刷新緩沖區(qū),如果 stream 為 NULL,就表示刷新所有 stdio 緩沖區(qū)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{printf("Hello World!\n");printf("Hello World!");fflush(stdout);// fclose(stdout);for ( ; ; )sleep(1);
}
上面代碼一樣可以看到兩個打印信息。同樣,關閉文件也可以刷新緩沖區(qū)
10. 文件描述符和FILE指針互轉
有時需要將文件 IO 和標準 IO 混合使用
#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);
當混合使用時,需要注意緩沖的問題,文件 IO 會直接將數(shù)據(jù)寫入到內核緩沖區(qū)進行高速緩存,而標準 IO 會將數(shù)據(jù)寫入到 stdio 緩沖區(qū),之后再調用 write() 將 stdio 緩沖區(qū)中的數(shù)據(jù)寫入到內核緩沖區(qū)。比如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main()
{printf("print");write(STDOUT_FILENO,"write\n",6);exit(0);
}
// 這里先輸出了write的內容,然后再輸出print的內容,因為print沒有遇到換行符,也就是清除緩沖區(qū),只有等到運行結束之后才清理