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); } }