引言
对于文件I/O,即打开文件,读文件,写文件这个应该都不太陌生,这篇文章会首先回忆一下C语言中的一些基础I/O,进而引入到Linux下的文件I/O。
C语言中的文件I/O
打开文件
#include<stdio.h>
FILE *fopen(const char *path,const char *mode)
返回值:
FILE* :文件指针
参数:
path:文件路径
mode:打开方式
这里的打开方式一共有六种:
- r:以只读方式打开
- r+:以读写的方式打开,写数据的话从文件的起始处开始
- w:以只写的方式打开,如果文件不存在,则创建该文件
- w+:以读写的方式打开,如果文件中已有数据,则删除原数据,如果文件不存在,则创建该文件
- a:以追加的方式写
- a+:以追加的方式写,如果文件不存在,则创建,如果文件存在,则清空文件开始写
读写数据
#include<stdio.h>
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);
返回值:读取的字节数
参数:
void *ptr:将数据读到哪个地方
size:这个类型的数据大小
nmemb:一共要读多少个这个类型的数据
stream:从哪里读数据
#include<stdio.h>
#include<string.h>
int main()
{
FILE* fp = fopen("myfile","w");
if(!fp)
{
printf("fopen error\n");
}
const char *msg = "hello world\n";
int count = 5;
while(count--)
{
fwrite(msg,strlen(msg),1,fp);
}
fclose(fp);
return 0;
}
调用fwrite创建了一个myfile文件,然后往里面写了五条hello world。
接下来再调用fread函数从myfile文件中读出这五条数据来。
代码如下:
#include<stdio.h>
#include<string.h>
int main()
{
FILE* fp = fopen("myfile","r");
if(!fp)
{
printf("fopen error\n");
}
char buf[1024];
const char *msg = "hello,bit!\n";
while(1)
{
size_t s = fread(buf,1,strlen(msg),fp);
if(s > 0)
{
buf[s] = 0;
printf("%s",buf);
}
if(feof(fp))
break;
}
fclose(fp);
return 0;
}
先从myfile文件中将数据读到buf中,然后输出buf。
linux下的文件I/O
操作Linux下的文件,首先要明白文件描述符,因为对文件的任何操作都是通过文件描述符来进行的。
系统默认打开的三个文件描述符:
stdin:标准输入
stdout:标准输出
syrerr:标准错误
在内核中,这三个文件描述符是被就是FILE*类型的。
新文件的文件描述符从最低位的不为空的开始累加。
这里用一段代码演示一下:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
close(1);//关闭标准输出的文件描述符
int fd = open("myfile",O_WRONLY|O_CREAT,0666);
if(fd < 0)
{
perror("open");
return -1;
}
printf("fd:%d\n",fd);
fflush(stdout);
close(fd);
return 0;
}
可以发现使用printf函数打印的话,没有输出到标准输出屏幕上,而是写到了myfile这个文件里,就是因为关闭了1这个文件描述符后,我们打开时创建的myflie文件拿到了1这个文件描述符,而printf函数只认文件描述符。
修改文件描述符的函数:
int dup(int oldfd);
int dup2(int oldfd,int newfd);
文件操作函数:
打开文件
int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);
返回值:文件描述符
参数:
pathname:文件的路径
flags:打开方式
1、O_RDONLY:只读打开
2、O_WRONLY:只写打开
3、O_RDWR:读写打开
这三个常量,必须指定一个且只能是一个
4、O_CREAT:若文件不存在,则创建,需要只能mode来指明新文件的权限
5、O_ARREND:追加写
mode:文件的初始权限
关闭文件函数:
int close(int fd);
直接给定要关闭的文件的文件描述符即可
像上图一样,所有的文件描述符都是在一个下标从0开始的数组里,所以是占有内核资源的,如果只打开而不关闭的话,肯定会造成资源浪费,最终导致系统资源耗尽。
文件写入函数:
ssize_t write(int fd,const void *buf,size_t count);
返回值:ssize_t是一个有符号的长整型,写入失败返回-1,写入成功返回写入的字节数
参数:
fd:所要操作的文件的文件描述符
buf:将数据往哪里写
count:一次写入的字节数
文件写入函数
ssize_t read(int fd,void *buf,siza_t count)
返回值:和write函数类似,读数据失败返回-1,成果返回读出的字节数
参数:
fd:所要操作的文件的文件描述符
buf:将数据往哪里读
count:一次读出的字节数
最后再来看一个现象总结一下:
#include<stdio.h>
int main()
{
const char *msg1 = "Hello,printf!\n";
const char *msg2 = "Hello,fwrite!\n";
const char *msg3 = "Hello,write!\n";
printf("%s",msg1);
fwrite(msg2,strlen(msg2),1,stdout);
write(1,msg3,strlen(msg3));
fork();
return 0;
}
同时使用printf,fwrite,write三个函数往标准输出屏幕上写一句话。
发现输出到标准输出上没毛病,就是三条结果。
再让他们输出到文件里看下效果:
输出到文件里竟然变成了五条结果:
而且发现是printf个fwrite两个输出了两次,解释一下:
- 标准输出采用的是行缓冲机制
- 文件内采用的是全缓冲机制
- write是立即输出,不会经过缓冲区
- 子进程会拷贝父进程的缓冲区
当写入文件内时,由于文件采用全缓冲机制,所以printf和fwrite两条输出在缓冲区里还有一份,而fork出来的子进程会拷贝父进程的缓冲区,所以这两条打印了两次。
以上就是关于文件I/O的一点小小总结。