IO文件操作時最常用的也最基本的內容。linux文件系統是由兩層結構構建:第一層是虛擬文件系統(VFS),第二層是各種不同的具體文件系統。
VFS是吧、把各種具體的文件系統的公共部分抽取出來,形成一個抽象層,是系統內核的一部分。它位於用戶程序和具體的文件系統中間。它對用戶
程序提供了標准的文件系統的調用接口,對具體的文件系統,它通過一系列的對不同文件系統公用的函數指針來實際調用具體的文件系統函數,完成實際
的各有的操作。任何使用文件系統的程序必須經過這層接口來使用它。通過這種方式,VFS就對用於屏蔽了底層文件系統的實現細節和差異。
通過 cat /proc/filesystems命令可以查看系統支持哪些文件系統。
open是系統調用級別的操作,調用VFS_open -> 底層的open(包括驅動的open,比如字符設備驅動里面的
file_opreations里面的open等。聯想一下)
一切接文件的理念。
標准IO庫
文件的常見操作
fopen/fclose
fgets/fputs
fgetc/fputc
fwrite/fread
定義:文件是一組相關數據的有序集合
按類型分類 :b,c,d,_,l,s,p
標准IO庫的所有操作都是圍繞流(stream)來進行的。
流:文本流,二進制流
當打開一個流時,標准IO函數fopen返回一個指向FILE對象的指針。該對象通常是一個結構,它包含了標准IO庫為管理該流所需要的所有信息,包括:用於實際IO的文件描述符、指向用於該流緩沖區的指針、緩沖區的長度、當前在緩沖區中的字符數以及出錯標志等等。
對於進程,預定了三個流,並且這三個流可以自動的被進程使用,他們是標准輸入、標准輸出和標准出錯。
2.文件緩沖
目的:盡量減少使用 read/write的調用次數
定義:系統自動的在內存中為每一個正在使用的文件開辟一個緩沖區,從內存向磁盤輸出數據必須先送到內存緩沖區,裝滿緩沖區在一起送到磁盤中去。從磁盤中讀數據,則一次從磁盤文件將一批數據讀入到內存緩沖區中,然后在從緩沖區逐個的將數據送到程序的數據區。
分類:全緩存、行緩存、不緩存。
全緩沖:在填滿標准IO緩沖區后才進行實際IO操作。對於駐留在磁盤上的文件通常是由標准IO庫實施全緩沖的。在一個流上執行第一次IO操作時,相關標准IO函數通常調用malloc獲得需使用的緩沖區。
flush,說明標准IO緩沖區的寫操作。緩沖區可由標准IO歷程自動沖洗,或者可調用函數fflush沖洗一個流。
行緩存 : 這種情況下,當在輸入和輸出中遇到換行符時,標准IO庫執行IO操作。這允許我們一次輸出一個字符(用標准IO fputs函數),但只有在謝了一行之后才進行實際IO操作。當流涉及一個終端時(例如標准輸入和輸出),通常使用行緩沖。
不緩存:不對字符進行緩沖。
很多的人機交互界面要求不可全緩沖。
標准出錯絕不會是全緩沖。
fopen(const char *path,const char *mode)函數:
參數:path 指定的一個文件路徑,包括文件
mode r,w,a
格式化輸出
執行格式化輸出處理的是4個printf函數。
#include<stdio.h>
int printf()將格式化數據寫到標准輸出
int fprintf()寫至指定的流
int sprintf()將格式化的字符送入數組buf中,在該數組的尾端自動加入一個null字節,但該字節不包括在返回值中,它可能會造成有buf指向的緩沖區溢出。調用者要保護該緩沖區足夠大。為了解決這種緩沖區溢出問題,引入了snprintf()函數。在該函數中,緩沖區長度是一個顯示參數,超過緩沖區尾端寫的任何字符都會被丟棄。如果緩沖區足夠大,snprintf函數就會返回寫入緩沖區的字符數。
利用fgets fputs實現文件的拷貝 #include <stdio.h> int main(int argc, char **argv) { int ch; char buf[1024]; //char *buf = malloc(1024); FILE *fp1,*fp2; if(argc != 3) { printf("Using : %s srcfilename decfilename\n", argv[0]); return -1; } if((fp1 = fopen(argv[1], "r")) == NULL) { perror("fopen 1"); return -1; } if((fp2 = fopen(argv[2], "w")) == NULL) { perror("fopen 2"); return -1; } while(fgets(buf, sizeof(buf), fp1) != NULL) { fputs(buf, fp2); } fclose(fp1); fclose(fp2); return 0; }
2.標准字符的讀入讀出
fgetc和fputc一次讀或者寫一個字符,如果流是帶緩存的,由標准IO函數處理所有緩存。
getchar等價於getc(stdin)。
三個函數的返回值:若成功則為下一個字符,若已處文件尾端或出錯則為EOF(ctrl+D,文件結束標志)
不管是出錯還是到達文件尾端,這三個函數都返回同樣值。為了區分這兩種不同的情況,必須調用ferror或者feof。
getc的實現是一個宏,fgetc是一個函數。
返回值為int類型。
一般用fgetc和fputc
#include <stdio.h> #include <errno.h> #include <string.h> int main(int argc, char *argv[]) { FILE *fpr, *fpw; int ch; if (argc < 3) { fprintf(stdout, "usage:%s srcfile destfile\n", argv[0]); return -1; } if ((fpr = fopen(argv[1], "r")) == NULL) { perror("fopen for reading"); return -1; } if ((fpw = fopen(argv[2], "w")) == NULL) { perror("fopen for writing"); return -1; } while ((ch = fgetc(fpr)) != EOF) fputc(ch, fpw); fclose(fpr); fclose(fpw); return 0; }
3.標准行IO
第一組,gets()\puts()不建議使用,因為這兩個函數沒有給出取得的數據的大小,一不小心就會造成內存溢出。
fgets(char *s,int size,FILE *stream)
fgets必須指定緩存的長度n,此函數一直讀到下一個新行的換行符為止,但是不超過n-1個字符,讀入的字符被送入到緩存。該緩存以null字符結尾。
即fgets一行后會加入一個'\0'
fgets的返回值是,如果執行成功,返回buf,若出錯或者處於文件結尾,就返回null。
這個特性可以判斷是否讀到文件尾了。
#include <stdio.h>
int main()
{
char buf[10];
while(1)
{
fgets(buf, 10, stdin);
printf("buf = ");
fputs(buf, stdout);
}
}上述程序只能輸出九個字符,因為第十個會被"\0"沖掉
顯示當地時間: #include <time.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv) { char buffer[4] = {0,0,0,':'}; char buffer2[50] = {0}; FILE *fp; struct tm *T; time_t time_temp; int count = 0, k = 0; if((fp = fopen(argv[1],"a+")) == NULL) //以追加方式寫入 { fprintf(stdout,"open file %s error!!!\n",argv[1]); exit(EXIT_FAILURE); } fseek(fp,0L,SEEK_SET); while(fgets(buffer2,50,fp) != NULL) { if(*buffer2 == '\n') continue; if(*(buffer2 + strlen(buffer2) - 1) == '\n') { count++; } } while(1) { count++; time_temp = time(NULL); T = localtime(&time_temp); fprintf(fp,"%3d : %d-%d-%d %d : %d : %2d\n",count, T->tm_year + 1900, T->tm_mon, T->tm_mday,T->tm_hour, T->tm_min, T->tm_sec); fflush(fp); sleep(1); } fclose(fp); return 0; }
4.fread,fwrite可以讀寫二進制文件
比如可以讀浮點數組、結構體之類的。
利用兩個函數實現文件拷貝 #include <stdio.h> int main(int argc, char **argv) { int nbyte; char buf[1024]; FILE *fp1,*fp2; if(argc != 3) { printf("Using : %s srcfilename decfilename\n", argv[0]); return -1; } if((fp1 = fopen(argv[1], "r")) == NULL) { perror("fopen 1"); return -1; } if((fp2 = fopen(argv[2], "w")) == NULL) { perror("fopen 2"); return -1; } while((nbyte = fread(buf, 1, 100, fp1)) > 0) { fwrite(buf, 1, nbyte, fp2); } fclose(fp1); fclose(fp2); return 0; }
5..fseek()
設定stream流文件位置指示
fseek(pd,long offset,whence)
whence三個參數:SEEK_SET 流頭
SEEK_CUR 當前的位置
SEEK_END 流結尾
例子:插入hello #include <stdio.h> #include <string.h> #define BUFSIZE 100 int main(int argc, char **argv) { char buf[100]; char insert_buf[] = "hello world\n"; FILE *fp1, *fp2; int line = 0; if((fp1 = fopen(argv[1], "r+")) == NULL) { perror("fopen1"); return -1; } if((fp2 = fopen("tmp", "w+")) == NULL) { perror("fopen1"); return -1; } int flag = 1; while(fgets(buf, BUFSIZE, fp1) != NULL) { fputs(buf, fp2); if((strlen(buf) < BUFSIZE - 1) || (buf[BUFSIZE - 2] == '\n')) { line++; } if((line == 3) && (flag == 1)) { flag = 0; fputs(insert_buf, fp2); } } fseek(fp1, 0, SEEK_SET); fseek(fp2, 0, SEEK_SET); while(fgets(buf, BUFSIZE, fp2) != NULL) { fputs(buf, fp1); } fclose(fp1); fclose(fp2); remove("tmp"); return 0; } 綜合練習1.寫time日志文件 #include <stdio.h> #include <time.h> #include <string.h> #define BUFSIZE 100 int main() { time_t t; struct tm *tm; FILE *fp; int line = 0; char buf[BUFSIZE]; if((fp = fopen("test.txt", "a+")) == NULL) { perror("fopen"); return -1; } while(fgets(buf, BUFSIZE, fp) != NULL) { if((strlen(buf) < BUFSIZE - 1) || (buf[BUFSIZE - 2] == '\n')) { line++; } } while(1) { time(&t); tm = localtime(&t); fprintf(fp, "%06d %04d-%02d-%02d %02d:%02d:%02d\n",line, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); fprintf(stdout, "%06d %04d-%02d-%02d %02d:%02d:%02d\n",line, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); line++; fflush(fp); sleep(1); } }