• Linux C文件操作


    linux下的文件操作包括两种,一种是使用C函数,一种是使用系统调用。

    • gcc 常用来实现c程序的编译
    • gcc filename.c 编译,链接(自动)后输出可执行文件a.out
    • 只是输入./a.out就可以执行filename程序
    • gcc -o filename filename.o 可以生成一个filename的可执行文件,直接执行filename就可以直接执行代码中的内容(与上一条相比,只是执行输出的名称)
    • gcc -S filename.c 可以生成一个filaname.s的汇编文件,汇编文件与底层执行时一一对应的,可以用来调试
    • gcc -O 可以用来编译优化,但是不是所有情况下都可以做编译优化。
    • 还可以使用gcc -c filename.cgcc -o filename filename.o分步编译、链接

    文件操作函数

    文件操作函数包括fopen、fgetc、fputc函数等,这是标准的C函数

    // 把file.in中给的内容输出给file.out
    #include
    #include
    
    int main(){
    	int c;
    	FILE *in,*out;//定义两个文件指针类型
    	in=fopen("file.in","r");//以只读的方式打开file.in文件,成功后返回文件指针in
    	out=fopen("file.out","w");//以只写的方式打开file.out文件,成功后返回文件out
    	while((c=fgetc(in))!=EOF)//从in文件中读,把读出来的数据写到out中
    		fputc(c,out);
    	fclose(in);
    	fclose(out);
    	exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • FILE * fopen(char *path,char *mode),第一个参数表示路径,第二个参数表示模式,fopen的返回值是一个文件指针,如果文件打开失败则返回一个NULL,可以使用fclose()函数关闭文件:

      • 模式包括
      • r表示以只读的方式打开,文件指针指向了文件的起始位置,r表示的文件必须存在。
      • w表示以写入的方式打开,如果文件不存在则创建文件,如果文件存在则先清空文件。
      • a表示以追加模式打开,对一个文件的写入,如果文件不存在就创建文件,如果文件存在就在文件末尾追加内容。
      • 如果后面有+表示可读写;
      • 如果后面有b表示打开的是二进制文件
      • 如果后面有t表示打开的是文本文件。
    • char *fgets(char *s,int size,FILE *stream)表示从指定的文件中读下一个字符,返回一个没有符号的字符,或者EOF(end of file),需要读的文件必须是读或者读写的方式打开的,并且文件存在。

    • fputc(char c,FILE * fp)把内容写入到指定文件。

    • 可以使用diff file.in file.out比较两个文件的内容,没有输出表示两个文件一模一样。

    • fclose,最重要的是把文件从内存中写回磁盘。

    为了程序的健壮性,可以把以上的文件改为以下,防止读的文件没有权限或者不存在

    // 把file.in中给的内容输出给file.out
    #include
    #include
    
    int main(){
    	int c;
    	FILE *in,*out;//定义两个文件指针类型
    	if((in=fopen("file.in","r"))==NULL){//以只读的方式打开file.in文件,成功后返回文件指针in
    		print("file.in文件不存在!")
    		return 0;
    	}
    	out=fopen("file.out","w");//以只写的方式打开file.out文件,成功后返回文件out
    	while((c=fgetc(in))!=EOF)//从in文件中读,把读出来的数据写到out中
    		fputc(c,out);
    	
    	exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    文件系统调用

    文件描述符以及open、read、write

    #include
    #include
    #include
    #include
    
    int main(){
    	char block[1024];
    	int in,out;
    	int nread;
    	
    	if((in=open("file.in",O_RDONLY))==-1){// 3
    		print("文件打开错误!");
    		return 0;}
    	out=open("file.out",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);// 4
    	while((nread=read(in,block,sizeof(block)))>0)
    		write(out,block,nread);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • int open(constchar*pathname,int flags,mode_t mode);如果成功则返回一个文件描述符(integer,小的非负整数且没有被使用过),否则返回-1;对于open函数来说,第三个参数当且仅当创建新文件(使用O_RDONLY)才是用,用于指定文件的访问权限,比如权限为777;pathname表示要打开或者创建的文件的路径名;flags表示指定文件的打开模式,flags的值包括以下,打开或者创建文件时,至少要使用一个以下命令:

      • O_RDONLY:只读模式
      • O_WRONLY:只写模式
      • O_RDWR:读写模式
      • O_CREAT:文件不存在时创建文件
    • mode的值包括以下

      • S_IRUSR:允许文件的所有者阅读它
      • S_IWUSR:允许文件所有者写它
      • S_IRGRP:允许文件组读取它
      • S_IWGRP: 允许文件组编写它

    linux下任何一个进程都有3个默认打开的文件描述符:0表示标准输入(键盘),1表示标准输出(显示器),2表示标准错误输出(显示器)

    • ssize_t read(int fd, void *buf, size_t count);表示读取指定字节数的数据到缓冲区buff中,同时文件的当前位置向后移动;读取成功返回读取的字节数,出错返回-1并且设置errno,如果在调用read之前已经到达文件末尾则返回0(例如,距文件末尾还有30个字节而请求读100个字节,则read返回30,下次read将返回0);

    • ssize_t write(int fd, const void *buf, size_t count); 成功返回写入的字节数,出错返回-1并设置errno写常规文件时

    系统调用与标准函数c的调用的区别

    使用time 文件路径,可以知道文件运行所需要的时间
    使用scrace 文件路径,可以跟踪文件

    • 使用fgetc或者fputc的时候,它底层的调用还是调用了read和write,但是在读取的时候做了一个系统的优化,也就是一次性读取了多个字节的内容,表面上看起来只读取一个字符,实际上底层中读取的是多个字节的字符。

    • 注意系统调用这个读写位置和使用C标准I/O库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用C标准I/O库时的读写位置是用户空间I/O缓冲区中的位置。比如用fgetc读一个字节,fgetc有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE结构体中记录的读写位置是1

    文件的读取位置

    标准c函数

    标准的c函数中,不可以对FILE的文件指针直接进行++,–,可以使用fseek函数来控制文件的读写指针。

    • int fseek(FILE *stream, long offset, int fromwhere);重定位流上的文件指针,函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置;返回值: 成功,返回0,否则返回其他值;第一个参数stream为文件指针;第二个参数offset为偏移量,整数表示正向偏移,负数表示负向偏移;第三个参数origin设定从文件的哪里开始偏移,可能取值为:

      • SEEK_SET: 文件开头,用0表示
      • SEEK_CUR: 当前位置,用1表示
      • SEEK_END: 文件结尾,用2表示
    • fseek(fp,100L,0);把fp指针移动到离文件开头100字节处;
      fseek(fp,100L,1);把fp指针移动到离文件当前位置100字节处;
      ffseek(fp,-100L,2);把fp指针退回到离文件结尾100字节处。

    系统调用

    系统调用使用lseek来调用文件的文件位置,与c函数调用相比,c文件调用使用的是文件指针,而系统调用使用的是文件描述符号

    • off_t lseek(int fd, off_t offset, int whence);fd表示函数描述符,参数含义与上面的c函数调用一致。

    空洞文件

    可以使用以上的fseek和lseek去创建一个空洞文件
    ① 空洞文件就是这个文件有一段是空的;
    ② 普通文件中间不能有空,write文件时从前往后移动文件指针,依次写入;
    ③ 用lseek往后跳过一段,就形成空洞文件;
    ④ 空洞文件对多线程共同操作文件非常有用。需要创建一个很大文件时,从头开始依次创建时间很长,可以将文件分成多段,多个线程操作每个线程负责其中一段的写入。

    文件的内存映射操作

    • mmap是一种内存映射文件的方法,即将一个文件或者其他的映射对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟控件中一段虚拟地址的映射关系。实现这种映射关系之后,进程可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,也就是对文件的操作不必调用read或者write等系统调用函数,相同的,在内核空间内这一段的区域也直接改为用户空间,从而实现不同进程的文件的共享。
    • 使用mmap进行映射,会得到一个磁盘和某一个文件的地址相同的地址,当往这个地址写内容的时候,内容会直接写到文件中,比正常的系统调用要少一次拷贝的过程。
    • 正常调用写文件的流程图
      在这里插入图片描述

    • mmp内存映射写文件的流程图
      在这里插入图片描述

    • void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)第一个参数表示映射的地址,如果地址设置为NULL,内核会自动挑一个地址来进行映射;第二个参数表示映射有多长;第三个参数描述了要求的内存保护,包括PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE;最后一个参数表明如果对内存进行修改能否被其他的进程看到,包括MAP_SHARED和MAP_PRIVATE。

    #include
    #include
    #include
    #include
    #include
    
    typedef strucy{
    	int integer;
    	char string[24];
    }RECORD;
    
    #define NRECORDS(100)
    
    int main(){
    	RECORD record,*mapped;
    	int i,f;
    	FILE *fp;
    	
    	fp=fopen("records.dat","w+");
    	for(i=0;i<NRECORDS;i++){
    		record.integer=i;
    		sprintf(record.string,"RECORD-%d",i);
    		fwrite(&record,sizeof(record),1,fp);//写record的数据,长度为record的大小,写一次,写到fp文件中
    	}
    	fclose(fp);
    	
    	fp=fopen("records.dat","r+");
    	fseek(fp,43*sizeof(record),SEEK_SET);
    	fread(&record,sizeof(record),1,fp);
    	//修改fp的数据
    	record.integer=143;
    	sprintf(record.string,"RECORD-%d",record.integer);
    	
    	//修改后写回
    	fseek(fp,-1*sizeof(record),SEEK_CUR);
    	fwrite(&record,sizeof(record),1,fp);
    	fclose(fp);
    
    	//----------------------------------------------------------
    	//使用mmap
    	f=open("records.dat",O_RDWR);
    	mapped=(RECORD *)mmap(0,NRECORDS*sizeof(record),PROT_RDAD|PROT_WRITE,MAP_SHARED,f,0);//第一个参数0表示内核挑选映射的地址;第二个参数表示映射的大小;第三个参数表示权限,可读可写;第四个参数表示可以共享;第五个参数表示被映射的文件;第六个参数表示映射从文件起始的位置开始的偏移量。执行完成后,整个文件全部映射到内存中,相当于一个数组mapped
    	mapped[43],integer=243;
    	sprintf(mapped[43].string,"RECORD-%d",mapped[43].integer);//操作数组一样操作内存
    	msync((void*)mapped,NRECORDS*sizeof(record),MS_ASYNC);//把内存和辅存中的文件同步起来
    	munmap((void*)mapped,NRECORDS*sizeof(record));//解开映射
    	close(f);
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    文件目录

    文件目录对应的是文件的属性信息,FCB文件控制块,存放文件的属性信息;文件的属性信息与文件的实体不是存在在一个位置上的,把文件的属性信息存放在一个位置,这一类的文件叫做目录文件,也就是windows上的文件夹

    #include
    #include
    #include
    #include
    #include
    #include
    
    void printdir(char *dir,int depth){
    	DIR *dp;
    	struct dirent *entry;//对应目录中的一项
    	struct stat statbuf;//文件的状态status
    	if((dp=opendir(dir))==NULL){//返回一个目录流
    		fprintf(stderr,"cannot open directory:%s\n",dir);
    		return;
    	}
    	chdir(dir);
    	while((entry=readdir(dp))!=NULL){//读一个dirent,并且指针指向了下一个dirent
    		lstat(entry->d_name,&statbuf);
    		if(S_ISDIR(statbuf.st_mode)){
    			if(strcmp(".",entry->d_name)==0||strcmp("..",entry->d_name)==0)
    				continue;
    		printf("%*s%s/\n",depth,"",entry->d_name);
    		printdir(entry->d_name,depth+4);//递归调用,深度优先搜索
    		}
    		else printf("%*s%s\n",depth,"",entry->d_name);
    	}
    	chdir("..");
    	closedir(dp);
    }
    
    int main(){
    	printf("Directory scan of /home:\n");
    	printdir("/home",0);
    	printf("done\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • #include :头文件

    • DIR *opendir(const char *name);:opendir函数用于打开指定的目录,并返回一个目录指针。

    • struct dirent *readdir(DIR *dirp):readdir函数用于读取目录项,它返回一个指向struct dirent的指针,该指针指向的结构体包含了目录项的信息。dirp参数是由opendir函数返回的目录指针。

    • int mkdir(const char *pathname, mode_t mode);:mkdir函数用于创建一个新的空目录。pathname参数是你想要创建的新目录的名称,mode参数则用于设置新目录的权限。

    • int chdir(const char *path);:path参数是你想要切换到的新工作目录的路径。

    • char *getcwd(char *buf, size_t size);:getcwd函数用于获取进程的当前工作目录。buf参数是一个字符数组的指针,用于存储返回的目录路径。size参数是buf数组的大小。

    • int closedir(DIR *dirp);:closedir函数用于关闭一个打开的目录。dirp参数是由opendir函数返回的目录指针。

    讨论

    请用代码展示使用标准c语言和使用系统调用来操控文件的区别

    讨论:使用标准的c函数操控文件的函数包括fopen、fgets、fputc、fclose等,并且使用标准c函数操控的是文件的指针类型;而使用系统调用操控文件的函数包括open、read、write等,使用系统调用操控的是文件的描述符。

    什么是mmap,怎么使用mmap来在多进程中共享内存

    讨论:mmap是内存映射文件的一种方法,即将一个文件或者其他的映射对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址的映射关系。使用mmap进行映射,会得到磁盘和某一个文件的地址相同的地址,实现内存的共享

    什么是文件夹?你能在文件夹里写点什么吗?为什么?描述文件夹的结构,并使用代码演示如何扫描文件夹。

    讨论:文件目录对应的是文件的属性信息,文件的属性信息与文件的实体不是存在在一个位置上的,把文件的属性信息存放在一个位置,这一类的文件叫做目录文件,也就是windows上的文件夹。
    代码看上面。

    写一个自己版本的grep命令

  • 相关阅读:
    面试题:Promise用法及理解
    @Elasticsearch之深度应用及原理剖析--Filter过滤机制剖析(bitset机制与caching机制)
    二叉树(相关术语、创建、遍历、最大深度问题)梳理总结
    自学Python 57 多线程开发(七)使用 Connection对象和共享对象 Shared
    day27IO(异常&File&综合案例)
    Redis持久化机制 RDB、AOF、混合持久化详解!如何选择?
    ES7~11学习48~68
    预防山体滑坡,泥石流监测智能预警系统
    如何系统地自学 Python
    使用css形变实现一个立方体
  • 原文地址:https://blog.csdn.net/CodePlayMe/article/details/133654217