文件的輸入/輸出操作

1.文件I/O操作概述

在Linux下,文件I/O操作可分爲兩類,一類是基於文件描述符的I/O操作,另一類是基於數據流的I/O操作。我們可以先來了解文件描述符和數據流這些基本概念。

1.1 文件描述符簡介
所謂的文件描述符,就是進程與打開的文件的一個橋樑。通過這個橋樑,纔可以在進程中對這個橋樑進行操作。
在Linux環境下,每打開一個磁盤文件,都會在內核中建立一個文件表項,文件表項裏存儲着文件的狀態信息、存儲文件內容的緩衝區和當前文件的讀寫位置。如果同一個磁盤文件打開了三次,就會創建3個這樣的文件表項(a,b,c),讀寫該文件時,只會改變文件表項中的讀寫位置。這3個文件表項存儲在一個文件表數組table[3]中,其中table[0] = a, table[1] = b, table[2] = c。這個文件表的下標就稱之爲文件描述符。將這個文件描述符存儲在一個數組des[3] = {0,1,2},那麼,在進程中就可以通過這個des數組下標引用文件表項。也就是說,通過文件描述符就可以訪問到這個磁盤文件。

概括地說,文件描述符就是一個小整數:分別是標準輸入0,標準輸出1,標準錯誤輸出2.它們對應的物理設備是鍵盤、顯示器、顯示器。

畫個圖幫大家理解:
在這裏插入圖片描述

1.2 數據流概述
從數據操作方式這個角度來說,Linux系統中的文件(普通文件與設備文件)可以看做是數據流。對文件操作之前,必須先調用標準I/O庫函數fopen()將數據流打開。打開後,就可以對數據流進行輸入和輸出操作。
要對數據流進行讀寫操作,需要標準I/O庫函數和FILE類型的文件指針一起來實現。這個文件指針是打開數據流時返回的指針,該指針用來表示要操作的數據流。當執行程序時,有3個數據流不需要進行特定的函數進行打開操作,它們會自動打開。這3個數據流是標準輸入、標準輸出、和標準錯誤輸出。它們自動打開,當不使用時,也會自動關閉。
然而,調用標準I/O庫函數fopen()打開數據流,在對數據流進行操作後,需要調用fclose()函數將其關閉。fclose()函數在關閉數據流之前,會清空在操作過程中分配的緩衝區並保存數據信息。

2.基於文件描述符的I/O操作

基於文件描述符的這些I/O操作,都是Linux操作系統提供的一組文件操作的接口函數,如open(),close(),read(),write(),lseek()等。

2.1 文件的打開與關閉
要對一個文件進行操作,前提是它已經存在,然後才能打開。打開後就可以對其操作或控制。在操作完成後,需要將其關閉,如果不及時關閉,就可能造成文件中數據的丟失。
在Linux中,提供了系統調用函數open(),close()用於打開和關閉一個已經存在的文件。

2.1.1 open()函數
該函數可以打開或創建一個文件(包括設備文件),其定義如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
int creat(const char* pathname, mode_t mode);
//函數具體使用哪個,和具體應用場景有關。
//如果目標文件不存在,需要open()創建,則第三個參數創建文件的默認權限。
//否則使用有兩個參數的open()函數。

上述的三個函數在調用成功時,都會返回其新分配的文件描述符;否則返回值爲-1,並設置適當的errno值。
參數:
pathname均代表要打開或創建的這個文件的路徑名稱;
flags代表文件打開方式的宏定義;
(O_RDONLY: 只讀打開;)
(O_WRONLY: 只寫打開;)
(O_RDWR: 讀寫打開;)
(O_CREAT: 若文件不存在,則創建。需要使用mode選項來指明新文件的訪問權限;)
(O_APPEND: 追加寫)
mode均代表文件的訪問權限。

2.1.2 close()函數
該函數用於關閉一個已經打開的文件,其定義如下:

#include <unistd.h>
int close(int fd);

如果調用成功,則返回0;失敗,返回-1,並設置適當的errno值。
參數fd是要關閉的文件描述符。

2.2 文件的讀寫操作
在Linux系統中,提供了系統調用函數read()和write(),用於實現文件的讀寫操作。

2.2.1 read()函數
該函數從打開的文件(包括設備文件)中讀取數據,該函數定義如下:

#include <unistd.h>
ssize_t read(int fd, void* buf, size_t count);

參數:
fd代表的是要進行讀寫的文件的文件描述符;
buf代表的是讀取的數據存放在buf指針所指向的緩衝區中;
count代表的是讀取的數據的字節數。
讀取文件數據時,文件的當前讀寫位置會向後移動。
注意:這個讀寫位置和使用C標準I/O庫時的讀寫位置有可能不同。這個讀寫位置是記在內核中的,而使用C標準I/O庫時的讀寫位置是用戶空間I/O緩衝區的位置。
如果調用成功,返回值爲讀取的字節數;否則返回值-1,並設置適當的errno值。

2.2.2 write()函數
該函數向打開的設備或文件中寫入數據,其定義如下:

#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);

參數:
fd代表想要寫入數據的文件的文件描述符;
buf指向寫入文件的數據的緩衝區;
count代表寫入文件的數據的字節數。
調用成功返回寫入的字節數,否則返回-1,並設置適當的errno值。
說明:當向常規文件寫入數據時,返回值是字節數count; 但是當向終端設備或者網絡中寫入時,返回的不一定爲寫入的字節數。

2.2.3 文件的定位
每個打開的文件都記錄着當前的讀寫位置,打開文件時讀寫位置是0,表示文件開頭,通常讀寫多少個字節就會將讀寫位置往後移動多少個字節。

lseek()函數可以移動當前的讀寫位置,通常也叫做偏移量,定義如下:

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fileds, off_t offset, int whence);

參數:
fileds代表文件名描述符;
offset代表偏移量;
whence代表用於偏移時的相對位置,其取值如下:
(SEEK_SET: 從文件開頭計算偏移量;)
(SEEK_CUR: 從當前位置計算偏移量;)
(SEEK_END: 從文件末尾計算偏移量;)
偏移量允許超過文件末尾,這種情況下對該文件的下一次寫操作將延長文件,未寫入內容的空間用’\0’填滿。
函數調用成功返回新的偏移量,否則返回-1,並設置新的errno值。

例:通過調用上述的幾種系統調用函數,對文件進行簡單的讀寫操作。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    char* path = "/root/file/oldfile.txt"; /*進行操作的文件路徑*/
    int fd;
    char buf[40], buf2[]="hello mrcff"; /*自定義讀寫用的緩衝區*/
    int n, i;
    if((fd = open(path, O_RDWR))<0)
    {
	perror("open file failed!");
	return 1;
    }
    else
	printf("open file successful!\n");
    if((n = read(fd, buf, 20))<0)
    {
	perror("read failed!");
	return 1;
    }
    else
    {
	printf("output read data:\n");
	printf("%s\n", buf); /*將讀取的數據輸出到終端控制檯*/
    }
    if((i = lseek(fd, 11, SEEK_SET))<0) /*定位到從文件開頭處到第11個字節處*/
    {
	perror("lseek error!");
	return 1;
    }
    else
    {
	if(write(fd, buf2, 11)<0)  /*向文件中寫入數據*/
	{
	    perror("write error!");
	    return 1;
	}
	else
	{
	    printf("write successfully!\n");
	}
    }
    close(fd); /*關閉文件的同時保存對文件的改動*/

    if((fd = open(path, O_RDWR))<0)   /*打開文件*/
    {
	perror("open file failed!");
	return 1;
    }
    if((n = read(fd, buf, 40)) <0)
    {
	perror("read 2 error!");
	return 1;
    }
    else
    {
	printf("read the changed data:\n");
	printf("%s\n", buf);  /*將數據輸出到終端*/
    }
    if(close(fd)<0) /*關閉文件*/
    {
	perror("close failed!");
	return 1;
    }
    else
	printf("close successfully! Bye~\n");
    return 0;
}


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