基礎I/O總結

引言

對於文件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兩個輸出了兩次,解釋一下:

  1. 標準輸出採用的是行緩衝機制
  2. 文件內採用的是全緩衝機制
  3. write是立即輸出,不會經過緩衝區
  4. 子進程會拷貝父進程的緩衝區

當寫入文件內時,由於文件採用全緩衝機制,所以printf和fwrite兩條輸出在緩衝區裏還有一份,而fork出來的子進程會拷貝父進程的緩衝區,所以這兩條打印了兩次。

以上就是關於文件I/O的一點小小總結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章