文件
文件编程的一般步骤:
打开/创建文件→读取文件/写入文件→关闭文件#
1、在linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作,最后是close关闭文件即可
2、我们对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后边的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏。
3、文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫静态文件。当我们去open打开一个文件时,linux内核做的操作包括:内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(动态文件)
4、打开文件,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而并不是针对静态文件的。当然我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。
5、为什么不直接对块设备直接操作
块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活。
open#
参数说明
int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);
返回值:成功则返回 文件描述符,否则返回 -1
Pathname:要打开的文件名(含路径,缺省为当前路径)
Flags:
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 可读可写打开
当我们附带了权限后,打开的文件就只能按照这种权限来操作。以上这三个常数中应当只指定一 个。下列常数是可选择的:
-
O_CREAT 若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。
-
O_EXCL 如果同时指定了OCREAT,而文件已经存在,则出错。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> int main() { int fd; fd = open("./file1",O_RDWR|O_CREAT|O_EXCL,0600); if(fd == -1){ printf("file cunZai\n"); } return 0; }
-
O_APPEND 每次写时都加到文件的尾端。
-
O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0。
Mode:一定是在flags中使用了O_CREAT标志,mode记录待创建的文件的访问权限
文件描述符
1、对于内核而言,所有打开文件都有文件描述符引用。文件描述符是一个非负整数。
当打开一个现存文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时,用open和creat返回的文件描述符表示该文件,将其作为参数传递给read和write。
2、文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果操作这个动态文件,只需要用这个文件描述符区分。
3、文件描述符的作用域就是当前进程,出了这个进程文件描述符就没意义了
creat#
参数说明
int creat(const char *filename,mode_t mode);
Pathname:要打开的文件名(含路径,缺省为当前路径)
mode:创建模式
常见创建模式:
宏表示 数字
-
S_IRUSR 4 可读
-
S_IWUSR 5 可写
-
S_IXUSR 1 可执行
-
S_IRWXU 7 可读、写、执行
返回值:
creat()会返回新的文件描述词, 若有错误发生则会返回-1, 并把错误代码设给errno.错误提示:
EEXIST参数:pathname 所指的文件已存在.
EACCESS参数:pathname 所指定的文件不符合所要求测试的权限
EROFS:欲打开写入权限的文件存在于只读文件系统内
EFAULT参数:pathname 指针超出可存取的内存空间
EINVAL参数:mode 不正确.
ENAMETOOLONG参数:pathname 太长.
ENOTDIR 参数:pathname 为一目录
ENOMEM :核心内存不足
ELOOP 参数:pathname 有过多符号连接问题.
EMFILE:已达到进程可同时打开的文件数上限
ENFILE:已达到系统可同时打开的文件数上限
write#
函数名:write
头文件:<unistd.h>
函数原型: int write(int handle,void *buf,int len);
功能:获取打开文件的指针位置
参数:int handle 为要获取文件指针的文件句柄
void *buf 为要写入的内容
int len 为要写入文件的长度
返回值:返回实际写入文件内容的长度
read#
用于文件描述符对应的文件中读取数据]
ssize_t read(int fd,void*buf,size_t count)
参数说明:
fd: 是文件描述符, 从command line获取数据时,为0
buf: 为读出数据的缓冲区;
count: 为每次读取的字节数(是请求读取的字节数,读上来的数据保
存在缓冲区buf中,同时文件的当前读写位置向后移)
返回值:
成功:返回读出的字节数
失败:返回-1,并设置errno,如果在调用read
之前到达文件末尾,则这次read返回0
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd;
char *buf = "chenLichen hen shuai!";
fd = open("./file1",O_RDWR);
if(fd == -1){
printf("open file1 failed\n");
fd = open("./file1",O_RDWR|O_CREAT,0600);
if(fd > 0){
printf("create file1 success!\n");
}
}
printf("open susceess : fd = %d\n",fd);
// ssize_t write(int fd, const void *buf, size_t count);
int n_write = write(fd,buf,strlen(buf));
if(n_write != -1){
printf("write %d byte to file\n",n_write);
}
close(fd);
fd = open("./file1",O_RDWR);
char *readBuf;
readBuf = (char *)malloc(sizeof(char)*n_write + 1);
// ssize_t read(int fd, void *buf, size_t count);
int n_read = read(fd, readBuf,100);
printf("read %d ,context:%s\n",n_read,readBuf);
close(fd);
return 0;
}
lseek#
-
通常调用read或write每读写一个文件,就会改变文件的读写位置。在linux中同样可以使用lseek函数来修改文件偏移量,即读写位置。
#include <sys/types.h> #include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
参数说明:
fd : 文件描述符
offset : 文件偏移量(offset为负值表示往前偏移,正值则表示往后偏移)
whence : 偏移位置(SEEK_SET,SEEK_CUR,SEEK_END)
1. SEEK_SET表示从文件开头位置往后移动offset个字节,且offset只能为正数,如果offset为负数是没有意义的。
2. SEEK_CUR表示从当前文件的位置往前或往后移动offset个字节,如果offset为正数则表示往后移动,为负数则表示往前移动。
3. SEEK=END表示从文件末尾的位置往前或往后移动offset个字节,如果offset为正数则表示往后移动为负数则表示往前移动。
lseek(fd , 0 , SEEK_SET); //文件开始位置
lseek(fd , 0 , SEEK_END); //文件末尾位置
lseek(fd , 10 , SEEK_END); //从文件末尾往后移动10个字节
lseek(fd , -10 , SEEK_END); //从文件末尾往前移动10个字节
lseek(fd , 100 , SEEK_SET); //从文件开始往后移动100个字节
返回值说明:
成功返回文件当前读写位置相对于文件开始位置的偏移量(字节数),如果文件读写的位置是在末尾的话,返回值就是文件头与文件尾之间的字节数,也就是文件大小。失败返回-1并设置errno
注意几点:
1. 如果文件偏移量往回超出文件头位置,则返回-1,文件指针不变,还是处于原来的位置。
2. lseek并不适用与所有的文件类型,也就是说在管道,FIFO,socket或终端不能使用lseek函数,一旦调用将会失败,并设置errno为EPIPE。
实现文件cp操作
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fdSrc;
int fdDes;
char *readBuf=NULL;
if(argc != 3){
printf("pararm error\n");
exit(-1);
}
fdSrc = open(argv[1],O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET);
readBuf=(char *)malloc(sizeof(char)*size + 8);
int n_read = read(fdSrc, readBuf, size);
fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);
int n_write = write(fdDes,readBuf,strlen(readBuf));
close(fdSrc);
close(fdDes);
return 0;
}
修改程序配置文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
// SPEED=5
// LENG=100
// SCORE=90
// LEVEL=95
int main(int argc, char **argv)
{
int fdSrc;
int fdDes;
char *readBuf=NULL;
if(argc != 2){
printf("pararm error\n");
exit(-1);
}
fdSrc = open(argv[1],O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET);
readBuf=(char *)malloc(sizeof(char)*size + 8);
int n_read = read(fdSrc, readBuf, size);
char *p = strstr(readBuf,"LENG=");
if(p==NULL){
printf("not found\n");
exit(-1);
}
p = p+strlen("LENG=");
*p = 5;
lseek(fdSrc,0,SEEK_SET);
int n_write = write(fdSrc,readBuf,strlen(readBuf));
close(fdSrc);
return 0;
}
结构体实现读写
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
struct Test
{
int a;
char c;
};
int main()
{
int fd;
struct Test data[2] = {{100,'a'},{101,'b'}};
struct Test data2[2];
fd = open("./file1",O_RDWR);
int n_write = write(fd,&data,sizeof(struct Test)*2);
lseek(fd,0,SEEK_SET);
int n_read = read(fd, &data2, sizeof(struct Test)*2);
printf("read %d,%c \n",data2[0].a,data2[0].c);
printf("read %d,%c \n",data2[1].a,data2[1].c);
close(fd);
return 0;
}
fopen
#include <stdio.h>
#include <string.h>
//实现文件读取
int main()
{
FILE *fp;
int i;
char c;
fp = fopen("./test.txt","r");
while(!feof(fp)){// nonezero if reach end of file
c = fgetc(fp);
printf("%c",c);
}
fclose(fp);
return 0;
}
#include <stdio.h>
#include <string.h>
//写入文件
int main()
{
FILE *fp;
int i;
char *str = "chenlichen hen shuai o!";
int len = strlen(str);
fp = fopen("./test.txt","w+");
for(i=0;i<len;i++){
fputc(*str,fp);
str++;
}
fclose(fp);
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
//FILE *fopen(const char *path, const char *mode);
FILE *fp;
char *str = "chenlichen hen shuai";
char readBuf[128] = {0};
fp = fopen("./chen.txt","w+");
//size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
//ptr buf
//size sizeof char
// 个数
// which file
//两种写法
fwrite(str,sizeof(char),strlen(str),fp);
fwrite(str,sizeof(char)*strlen(str),1,fp);
int nwrite = fwrite(str,sizeof(char)*strlen(str),1,fp);
fseek(fp,0,SEEK_SET);
// size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
int nread = fread(readBuf,sizeof(char)*strlen(str),1,fp);
printf("read data: %s\n",readBuf);
printf("read=%d,write = %d\n",nread,nwrite);
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
struct Test
{
int a;
char c;
};
int main()
{
FILE *fp;
struct Test data = {100,'a'};
struct Test data2;
fp = fopen("./file1","w+");
int n_write = fwrite(&data,sizeof(struct Test),1,fp);
fseek(fp,0,SEEK_SET);
int n_read = fread(&data2, sizeof(struct Test), 1,fp);
printf("read %d,%c \n",data2.a,data2.c);
fclose(fp);
return 0;
}
open与fopen的区别
1. 来源#
从来源的角度看,两者能很好的区分开,这也是两者最显而易见的区别:
-
open
是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。 -
fopen是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。
PS:从来源来看,两者是有千丝万缕的联系的,毕竟C语言的库函数还是需要调用系统API实现的。
2. 移植性#
这一点从上面的来源就可以推断出来,fopen
是C标准函数,因此拥有良好的移植性;而open
是UNIX系统调用,移植性有限。如windows下相似的功能使用API函数CreateFile
。
3. 适用范围#
open
返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。- fopen是用来操纵普通正规文件(Regular File)的。
4. 文件IO层次#
如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态。
5. 缓冲#
- 缓冲文件系统
缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读出需要的数据。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind
等。 - 非缓冲文件系统
缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。open, close, read, write, getc, getchar, putc, putchar
等。
一句话总结一下,就是open
无缓冲,fopen
有缓冲。前者与read
, write
等配合使用, 后者与fread
,fwrite
等配合使用。
使用fopen
函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:read
,write
);而使用open
函数,在文件读写时则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen
系列的函数要比直接调用open
系列的函数快;如果随机访问文件则相反。
这样一总结梳理,相信大家对于两个函数及系列函数有了一个更全面清晰的认识,也应该知道在什么场合下使用什么样的函数更合适,效率更高。
作者:keep--fighting
出处:https://www.cnblogs.com/keep--fighting/p/17277549.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探