一,什么是標準I/O庫
標準c庫當中用于文件I/O操作相關的一套庫函數,實用標準I/O需要包含頭文件
二,文件I/O和標準I/O之間的區別
1.標準I/O是庫函數,而文件I/O是系統調用
2.標準I/O是對文件I/O的封裝
3.標準I/O相對于文件I/O具有更好的可移植性,且效率高
三,FILE文件指針
FILE是一個數據結構體,標準I/O實用FILE指針作為文件句柄
FILE文件指針用于標準I/O庫函數,而文件描述符則用于文件I/O系統調用,FILE數據結構定義在標準 I/O 庫函數頭文件 stdio.h 中
四,標準輸入,標準輸出和標準錯誤
標準輸入設備:計算機系統的標準的輸入設備
標準輸出設備:計算機所連接的鍵盤
輸出標準設備:計算機所連接的顯示器
五,標準I/O函數
1)打開文件:fopen()
FILE *fopen(const char *path, const char *mode);
path : 參數 path 指向文件路徑,可以是絕對路徑、也可以是相對路徑。mode : 參數 mode 指定了對該文件的讀寫權限![]()
2)關閉文件:fclose()
int fclose(FILE *stream);
stream 為 FILE 類型指針,也就是文件句柄,調用成功返回 0 ;失敗將返回 EOF (也就是 -1 )
3)讀取/寫入文件:fread()/fwrite()
fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
?ptr:fread()將讀取到的數據存放在參數 ptr 指向的緩沖區中
size : fread() 從文件讀取 nmemb 個數據項,每一個數據項的大小為 size 個字節,所以總共讀取的數據大小為 nmemb * size 個字節。nmemb : 參數 nmemb 指定了讀取數據項的個數。stream : FILE 指針返回項:讀取或者寫入的數據項的數目
寫入:
#include <stdio.h>
#include <stdlib.h>int main(void)
{char buf[] = "hello world!";FILE *fp = NULL;if(NULL == (fp = fopen("./test.txt","w+"))){perror("open error");return 1;}printf("open ok!!!\r\n");if(sizeof(buf)>(fwrite(buf,1, sizeof(buf), fp))){printf("fwrite error");fclose(fp);exit(-1);}printf("寫入成功\r\n");fclose(fp);return 0;
}
?運行結果:
?讀取:
#include <stdio.h>
#include <stdlib.h>int main(void)
{char buf[20] = "0";FILE *fp = NULL;int size;if(NULL == (fp = fopen("./test.txt","r"))){perror("open error");return 1;}printf("open ok!!!\r\n");if(12>(size = fread(buf,1, 11, fp))){if(ferror(fp)){printf("fread error");fclose(fp);exit(-1);}}printf("成功讀取%d 個字節數據: %s\n", size, buf);fclose(fp);return 0;
}
運行結果:
?4)定位函數:fseek()
int fseek(FILE *stream, long offset, int whence);
stream : FILE 指針。offset : 與 lseek() 函數的 offset 參數意義相同。whence : 與 lseek() 函數的 whence 參數意義相同
?5)判斷是否到達文件末尾--feof()函數
int feof(FILE *stream);
6)判斷是否發生了錯誤--ferror()函數
int ferror(FILE *stream);
7)清楚標志--clearerr()函數【自己獨立設置標志】
void clearerr(FILE *stream);
?8)格式化輸入
int printf(const char *format, ...);
將程序中的字符串信息輸出顯示到終端int fprintf(FILE *stream, const char *format, ...);
將格式化數據寫入到由 FILE 指針指定的文件int dprintf(int fd, const char *format, ...);
將格式化數據寫入到由文件描述符 fd 指定的文件int sprintf(char *buf, const char *format, ...);
將格式化數據存儲在由參數 buf 所指定的緩沖區中int snprintf(char *buf, size_t size, const char *format, ...);
使用參數 size 顯式的指定緩沖區的大小,如果寫入到緩沖區的字節數大于參數 size 指定的大
小,超出的部分將會被丟棄!如果緩沖區空間足夠大,snprintf()函數就會返回寫入到緩沖區的字符數,與
sprintf()函數相同,也會在字符串末尾自動添加終止字符'\0'
9)格式化輸出
int scanf(const char *format, ...);
scanf()函數將用戶輸入(標準輸入)的數據進行格式化轉換并進行存儲int fscanf(FILE *stream, const char *format, ...);
從指定文件中讀取數據,作為格式轉換的輸入數據,文件通過 FILE 指針指定int sscanf(const char *str, const char *format, ...);
從參數 str 所指向的字符串緩沖區中讀取數據,作為格式轉換的輸入數據
六,文件I/O緩沖
1.內核緩沖
read()和write()系統調用是在進行文件讀寫操作的時候并不會直接訪問磁盤設備,而是僅僅在用戶空間緩沖區和內核緩沖區之間復制數據。調用write()函數后,會將數據保存到緩存數據區,然后等待內核在某個時刻將緩沖區的數據寫入到磁盤設備中,但此時如果read()函數,會直接將數據緩存器的數據返回給應用程序。反之,同理
2.刷新文件I/O的內核緩沖區
對于一些操作,必須強制將文件I/O內核緩沖區中緩存的數據寫入到磁盤設備。
fsync()函數:
int fsync(int fd);
系統調用 fsync() 將參數 fd 所指文件的內容數據和元數據寫入磁盤,只有在對磁盤設備的寫入操作完成之后,fsync()函數才會返回,函數調用成功將返回 0 ,失敗返回 -1
fdatasync()函數:
int fdatasync(int fd);
系統調用 fdatasync() 與 fsync() 類似,不同之處在于 fdatasync() 僅將參數 fd 所指文件的內容數據寫入磁盤,并不包括文件的元數據
sync()函數:
void sync(void);
系統調用 sync() 會將所有文件 I/O 內核緩沖區中的文件內容數據和元數據全部更新到磁盤設備中,該函數沒有參數、也無返回值
3.控制文件I/O內核緩沖的標志
1.O_DSYNC 標志:write()調用之后調用 fdatasync()函數【元數據不同步】進行數據同步
2.O_SYNC 標志:write()調用都會自動將文件內容數據和元數據刷新到磁盤設備中
4.直接I/O:繞過內核緩沖
在open函數調用添加O_DIRECT就可以進行調用
直接 I/O 的對齊限制 :
? 應用程序中用于存放數據的緩沖區,其內存起始地址必須以塊大小的整數倍進行對齊;? 寫文件時,文件的位置偏移量必須是塊大小的整數倍;? 寫入到文件的數據大小必須是塊大小的整數倍。
5.stdio緩沖
用戶空間 的緩沖區,當應用程序中通過標準 I/O 操作磁盤文件時,為了減少調用系統調用次數,標準 I/O 函數會將用戶寫入或讀取文件的數據緩存在 stdio 緩沖區,然后再一次性 stdio 緩沖區中緩存的數據通過調用系統調用 I/O (文件 I/O )寫入到文件 I/O 內核緩沖區或者拷貝到應用程序的 buf 中
三種緩沖類型:
? _IONBF :不對 I/O 進行緩沖(無緩沖)。意味著每個標準 I/O 函數將立即調用 write() 或者 read() ,并且忽略 buf 和 size 參數,可以分別指定兩個參數為 NULL 和 0 。標準錯誤 stderr 默認屬于這一種類型,從而保證錯誤信息能夠立即輸出? _IOLBF :采用行緩沖 I/O 。在這種情況下,當在輸入或輸出中遇到換行符 "\n" 時,標準 I/O 才會執行文件 I/O 操作。對于輸出流,在輸出一個換行符前將數據緩存(除非緩沖區已經被填滿),當輸 出換行符時,再將這一行數據通過文件 I/O write() 函數刷入到內核緩沖區中;對于輸入流,每次讀取一行數據。對于終端設備默認采用的就是行緩沖模式,譬如標準輸入和標準輸出。? _IOFBF :
采用全緩沖 I/O 。在這種情況下,在填滿 stdio 緩沖區后才進行文件 I/O 操作( read 、 write ),對于輸出流,當 fwrite 寫入文件的數據填滿緩沖區時,才調用 write() 將 stdio 緩沖區中的數據刷入內核緩沖區;對于輸入流,每次讀取 stdio 緩沖區大小個字節數據。默認普通磁盤上的常規文件默認常用這種緩沖模式
刷新stdio緩沖區
int fflush(FILE *stream);強制進行文件的刷新,如果參數是NULL,則刷新所有的stdio緩沖區? 調用 fflush()庫函數可強制刷新指定文件的 stdio 緩沖區;
? 調用 fclose()關閉文件時會自動刷新文件的 stdio 緩沖區;
? 程序退出時會自動刷新 stdio 緩沖區(注意區分不同的情況)
I/O緩沖小結:
應用程序調用標準 I/O 庫函數將用戶數據寫入到 stdio 緩沖區中, stdio 緩沖區是由 stdio 庫所維護的用戶空間緩沖區。針對不同的緩沖模式,當滿足條件時, stdio 庫會調用文件 I/O (系統調用 I/O )將 stdio 緩沖區中緩存的數據寫入到內核緩沖區中,內核緩沖區位于內核空間。最終由內核向磁盤設備發起讀寫操作,將內核緩沖區中的數據寫入到磁盤(或者從磁盤設備讀取數據到內核緩沖區)![]()
七,文件描述符和FILE指針互轉
int fileno(FILE *stream);
將標準 I/O 中使用的 FILE 指針轉換為文件 I/O 中所使用的文件描述符成功:文件描述符 失敗:NULLFILE *fdopen(int fd, const char *mode);
將文件描述符轉換為FILE指針成功:文件指針 失敗:NULL
八,linux系統的文件類型
文本文件 :內容由文本構成
二進制文件:.o, .bin文件......
符號鏈接文件:指向另一個文件的路徑
管道文件:進程間通信
套接字文件:不同主機的進程間通信
獲取文件的屬性:stat
int stat(const char *pathname, struct stat *buf);
st_dev :該字段用于描述此文件所在的設備。不常用,可以不用理會。st_ino :文件的 inode 編號。st_mode:該字段用于描述文件的模式,譬如文件類型、文件權限都記錄在該變量中st_nlink :該字段用于記錄文件的硬鏈接數,也就是為該文件創建了多少個硬鏈接文件。鏈接文件可以分為軟鏈接(符號鏈接)文件和硬鏈接文件st_uid 、 st_gid :此兩個字段分別用于描述文件所有者的用戶 ID 以及文件所有者的組 IDst_rdev :該字段記錄了設備號,設備號只針對于設備文件,包括字符設備文件和塊設備文件,不用理會。st_size :該字段記錄了文件的大小(邏輯大小),以字節為單位。st_atim 、 st_mtim 、 st_ctim :此三個字段分別用于記錄文件最后被訪問的時間、文件內容最后被修改的時間以及文件狀態最后被改變的時間,都是 struct timespec 類型變量
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main(void)
{struct stat file_stat;int ret;ret = stat("./test.txt",&file_stat);if(-1 == ret){perror("open error");exit(-1);}printf("%ld %ld\r\n",file_stat.st_size,file_stat.st_ino);exit(0);}
運行結果:
fstat:相對于stat的區別就是,fstat是從fd去獲取文件的屬性,而stat是從文件路徑獲取的
lstat()與 stat、fstat 的區別在于,對于符號鏈接文件,stat、fstat 查閱的是符號鏈接文件所指向的文件對應的文件屬性信息
九,文件屬主
1.有效用戶ID和有效組ID
通常,絕大部分情況下,進程的有效用戶等于實際用戶(有效用戶 ID 等于實際用戶 ID),有效組等于實際組(有效組 ID 等于實際組 ID)
2.chown函數:改變文件的所屬者和所屬組
sudo chown root:root testApp.c
int chown(const char *pathname, uid_t owner, gid_t group);pathname:用于指定一個需要修改所有者和所屬組的文件路徑
owner:將文件的所有者修改為該參數指定的用戶(以用戶 ID 的形式描述);
group:將文件的所屬組修改為該參數指定的用戶組(以用戶組 ID 的形式描述);
返回值:成功返回 0;失敗將返回-1,兵并且會設置 errno
? 只有超級用戶進程能更改文件的用戶 ID ;? 普通用戶進程可以將文件的組 ID 修改為其所從屬的任意附屬組 ID ,前提條件是該進程的有效用戶 ID 等于文件的用戶 ID ;而超級用戶進程可以將文件的組 ID 修改為任意值
3.普通權限和特殊權限
普通權限:
進程對文件進行操作的時候、將進行權限檢查,如果文件的 set-user-ID 位權限被設置,內核會將 進程的有效 ID 設置為該文件的用戶 ID (文件所有者 ID ),意味著該進程直接獲取了文件所有者 的權限、以文件所有者的身份操作該文件
進程對文件進行操作的時候、將進行權限檢查,如果文件的 set-group-ID 位權限被設置,內核會 將進程的有效用戶組 ID 設置為該文件的用戶組 ID(文件所屬組 ID),意味著該進程直接獲取了文件所屬組成員的權限、以文件所屬組成員的身份操作該文件
3.sticky權限
?注:
4.檢查文件的權限
int access(const char *pathname, int mode); pathname:文件路徑mode:? F_OK:檢查文件是否存在
? R_OK:檢查是否擁有讀權限
? W_OK:檢查是否擁有寫權限
? X_OK:檢查是否擁有執行權限
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main(void)
{int ret;ret = access("./test.txt",F_OK);if(-1 == ret){printf("文件不存在/r/n");exit(-1);}ret = access("./test.txt",R_OK);if(!ret){printf("可以讀取\r\n");}else{printf("不可以進行讀取\r\n");}ret = access("./test.txt",W_OK);if(!ret){printf("可以寫入\r\n");}else{printf("不可以進行寫入\r\n");}ret = access("./test.txt",X_OK);if(!ret){printf("不可以進行執行\r\n");}else{printf("不可以進行執行\r\n");}return 0;
}
運行結果:
5.chmod修改文件的權限
int chmod(const char *pathname, mode_t mode);pathname:
需要進行權限修改的文件路徑,若該參數所指為符號鏈接,實際改變權限的文件是符號鏈
接所指向的文件,而不是符號鏈接文件本身。
mode:
該參數用于描述文件權限,與 open 函數的第三個參數一樣,這里不再重述,可以直接使用八進
制數據來描述,也可以使用相應的權限宏(單個或通過位或運算符" | "組合)
fchmod():根據fd進行文件權限的修改? ? ? ? ?
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>int main(void)
{int ret;ret = chmod("./test.txt", 0777);if(-1 == ret){perror("修改失敗");exit(-1);}return 0;
}
??
6.umask函數
文件的實際權限實際上不等于我們設置的權限
mode & ~umask? ? ? ? ? ? ? ? ? eg.? ?0777 & (~0002) = 0775
mode_t umask(mode_t mask);返回值是舊的mask 參數是 新設定的mask
十,文件的時間屬性
修改時間屬性: utime(),utimes()
int utime(const char *filename, const struct utimbuf *times);filename: 文件路徑struct utimbuf {time_t actime; /* 訪問時間 */time_t modtime; /* 內容修改時間 */
};int utimes(const char *filename, const struct timeval times[2]);filename: 文件路徑struct timeval {long tv_sec; /* 秒 */long tv_usec; /* 微秒 */
};相比之下:utimes的精度更高一些,可以更改到微秒級別
int futimens(int fd, const struct timespec times[2]);fd:文件描述符。
times:將時間屬性修改為該參數所指定的時間值,times 指向擁有 2 個 struct timespec 結構體類型變量
的數組,數組共有兩個元素,第一個元素用于指定訪問時間,第二個元素用于指定內容修改時間
#include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <sys/types.h> #include <time.h> #include <stdio.h> #include <stdlib.h> #define MY_FILE "./test.txt" int main(void) {struct timespec tmsp_arr[2];int ret;int fd;/* 檢查文件是否存在 */ret = access(MY_FILE, F_OK);if (-1 == ret) {printf("Error: %s file does not exist!\n", MY_FILE);exit(-1);}/* 打開文件 */fd = open(MY_FILE, O_RDONLY);if (-1 == fd) {perror("open error");exit(-1);}/* 修改文件時間戳 */#if 0ret = futimens(fd, NULL); //同時設置為當前時間#endif#if 1tmsp_arr[0].tv_nsec = UTIME_OMIT;//訪問時間保持不變tmsp_arr[1].tv_nsec = UTIME_NOW;//內容修改時間設置為當期時間ret = futimens(fd, tmsp_arr);#endif }
utimensat()函數:?
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);dirfd:
該參數可以是一個目錄的文件描述符,也可以是特殊值 AT_FDCWD;如果 pathname 參數指定
的是文件的絕對路徑,則此參數會被忽略。pathname:
指定文件路徑。如果 pathname 參數指定的是一個相對路徑、并且 dirfd 參數不等于特殊值
AT_FDCWD,則實際操作的文件路徑是相對于文件描述符 dirfd 指向的目錄進行解析。如果 pathname 參數
指定的是一個相對路徑、并且 dirfd 參數等于特殊值 AT_FDCWD,則實際操作的文件路徑是相對于調用進
程的當前工作目錄進行解析times:
與 futimens()的 times 參數含義相同flags :
此參數可以為 0 , 也可以設置為 AT_SYMLINK_NOFOLLOW , 如 果 設 置 為
AT_SYMLINK_NOFOLLOW,當 pathname 參數指定的文件是符號鏈接,則修改的是該符號鏈接的時間戳,
而不是它所指向的文件
?十一,符號鏈接()軟鏈接和硬鏈接
硬鏈接:
ls-li? ?查看當前的硬鏈接文件個數,源文件本身也是一個硬鏈接文件
各個硬鏈接文件的inode指向的是同一個文件
ln 源文件名稱? 新創建文件名稱? ? ? ? ? ? ? ? ? ? ? ? ?創建硬鏈接文件
創建硬鏈接:
int link(const char *oldpath, const char *newpath);
?軟鏈接:
ln -s? ? 源文件名稱? 新創建文件名稱? ? ? ? ? ? ? ? ? ? ? ? ?創建硬鏈接文件
當軟鏈接的源文件刪除,其余的文件被稱為“懸空鏈接”,原因:軟鏈接文件類似于一種“主從”?關系,軟鏈接內部存著源文件的路徑名,當源文件被刪除,則無法找到文件路徑
創建軟鏈接:
int symlink(const char *target, const char *linkpath);
讀取軟鏈接:
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);buf:存放文件緩沖區bufsiz: 讀取的鏈接文件的大小
?創建和刪除目錄:
int mkdir(const char *pathname, mode_t mode);int rmdir(const char *pathname);
?打開,讀取,關閉目錄:
DIR *opendir(const char *name);struct dirent *readdir(DIR *dirp);int closedir(DIR *dirp);
刪除文件:
int unlink(const char *pathname);
int remove(const char *pathname);pathname 參數指定的是一個非目錄文件,那么 remove()去調用 unlink(),如果 pathname 參數指定的是
一個目錄,那么 remove()去調用 rmdir()
十二,文件重命名
int rename(const char *oldpath, const char *newpath);
#include <stdio.h> #include <stdlib.h> int main(void) {int ret;ret = rename("./test", "./test_file");if (-1 == ret) {perror("rename error");exit(-1);}exit(0); }
?