引言
對於文件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的一點小小總結。