1. 程式人生 > 其它 >Linux(C/C++)程式設計:write、fwrite、read、fread

Linux(C/C++)程式設計:write、fwrite、read、fread

技術標籤:# C++

write/read是linux下的底層系統呼叫函式,fwrite與freadc/c++下的標準I/O庫函式

理論

#include <unistd.h>
/*
* 功能 : 從描述符為fd的當前檔案位置複製最多n個位元組到記憶體位置buf
* 引數:
* 	 fd        檔案指標
* 	 buf       讀上來的資料儲存在緩衝區buf中,同時檔案的當前讀寫位置向後移
*    __nbytes  是請求讀取的位元組數。若引數count 為0, 則read()不會有作用並返回0. 返回值為實際讀取到的位元組數, 如果返回0
* 返回值:
* (1)如果成功,返回讀取的位元組數;
* (2)如果出錯,返回-1並設定errno;
* (3)如果在調read函式之前已是檔案末尾,則返回0,表示EOF
*/
ssize_t read (int __fd, void *__buf, size_t __nbytes) /* *功能: 從記憶體位置buf中複製最多n個位元組到描述符fd的當前檔案位置 * 引數: fd 檔案描述符 * buf 指定的緩衝區 * nbyte 要寫入檔案的指定位元組數 * 返回值: * (1)成功,返回寫入的位元組數 * (2)寫入出錯,返回-1 * (3)如果寫的過程中遇到了中斷(緩衝區滿了),返回-1並且error=EINTR (eintr) * */ ssize_t write (int __fd, const void *__buf, size_t __n)
/* * 功能: 把 ptr 所指向的陣列中的資料寫入到給定流 stream 中。 * 引數: ptr -- 這是指向要被寫入的元素陣列的指標。 * size -- 這是要被寫入的每個元素的大小,以位元組為單位。 * nmemb -- 這是元素的個數,每個元素的大小為 size 位元組。 * stream -- 這是指向 FILE 物件的指標,該 FILE 物件指定了一個輸出流。 * 返回值:如果成功,該函式返回一個 size_t 物件,表示元素的總數,該物件是一個整型資料型別。如果該數字與 nmemb 引數不同,則會顯示一個錯誤。 */ size_t fwrite(const
void *ptr, size_t size, size_t nmemb, FILE *stream) /* * 作用: 從給定流 stream 讀取資料到 ptr 所指向的陣列中。 * 引數: ptr -- 這是指向帶有最小尺寸 size*nmemb 位元組的記憶體塊的指標。 * size -- 這是要讀取的每個元素的大小,以位元組為單位 * nmemb -- 這是元素的個數,每個元素的大小為 size 位元組 * stream -- 這是指向 FILE 物件的指標,該 FILE 物件指定了一個輸入流。 * 返回: 成功讀取的元素總數會以 size_t 物件返回,size_t 物件是一個整型資料型別。如果總數與 nmemb 引數不同,則可能發生了一個錯誤或者到達了檔案末尾。 */ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

read/write和fread/fwrite區別

1,fread是帶緩衝的,read不帶緩衝.

2,fopen是標準c裡定義的,open是POSIX中定義的.

3,fread可以讀一個結構.read在linux/unix中讀二進位制與普通檔案沒有區別.

4,fopen不能指定要建立檔案的許可權.open可以指定許可權.

5,fopen返回指標,open返回檔案描述符(整數).

6,linux/unix中任何裝置都是檔案,都可以用open,read.

如果檔案的大小是8k。你如果用read/write,且只分配了2k的快取,則要將此檔案讀出需要做4次系統呼叫來實際從磁碟上讀出。

如果你用fread/fwrite,則系統自動分配快取,則讀出此檔案只要一次系統呼叫從磁碟上讀出。

也就是用read/write要讀4次磁碟,而用fread/fwrite則只要讀1次磁碟。效率比read/write要高4倍。

如果程式對記憶體有限制,則用read/write比較好。

都用fread 和fwrite,它自動分配快取,速度會很快,比自己來做要簡單。如果要處理一些特殊的描述符,用read和write,如套介面,管道之類的

系統呼叫write的效率取決於你buf的大小和你要寫入的總數量,如果buf太小,你進入核心空間的次數大增,效率就低下。而fwrite會替你做快取,減少了實際出現的系統呼叫,所以效率比較高。

實踐

一般用法

  1. write
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int fd; char string[40];
    int length, res;
    /* Create a file named "TEST.$$$" in the current directory and write a string to it. If "TEST.$$$" already exists, it will be overwritten. */
    if ((fd= open("TEST.$$$", O_WRONLY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE)) == -1)
    {
        printf("Error opening file.\n");
        exit(1);
    }
    strcpy(string, "Hello, world!\n");
    length = strlen(string);
    if ((res = write(fd, string, length)) != length)
    {
        printf("Error writing to the file.\n");
        exit(1);
    }
    printf("Wrote %d bytes to the file.\n", res);
    close(fd);
    return 0;
}
  1. fwrite
#include<stdio.h>
 
int main ()
{
   FILE *fp;
   char str[] = "This is runoob.com";
 
   fp = fopen( "file.txt" , "w" );
   fwrite(str, sizeof(str) , sizeof(char), fp );
 
   fclose(fp);
  
   return(0);
}

3、一次一個位元組從標準輸入複製到標準輸出

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main ()
{
    char c;
    while (read(STDIN_FILENO, &c, 1) != 0){
        write(STDIN_FILENO, &c, 1);
    }
    return(0);
}

在這裡插入圖片描述

位元組流套接字封裝

位元組流套接字(比如TCP套接字)上的read和write函式所表現的行為不同於通常的檔案IO。位元組流套接字上呼叫read或者write輸入或者輸出的位元組數可能比請求的數量少,然而這不是出錯的狀態。這個現象的原因在於核心中用於套接字的緩衝區可能已經達到了極限。此時需要呼叫者再次呼叫read或者write函式,以輸入或者輸出剩餘的位元組。

  1. 封裝write
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>


ssize_t	writen(int fd, const void *vptr, size_t n)
{
    size_t		nleft;
    ssize_t		nwritten;
    const char	*ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR){
				 nwritten = 0;   // 緩衝區滿了,忽略這個錯誤,繼續開始write	
			} else{
				return(-1);	
			}  	
        }

        nleft -= nwritten;
        ptr   += nwritten;
    }
    return(n);
}

// 往一個描述符寫n位元組
void Writen(int fd, void *ptr, size_t nbytes)
{
    if (writen(fd, ptr, nbytes) != nbytes){
        printf("writen error");
        exit(0);
    }

}

2、封裝read

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>


ssize_t readn(int fd, void *vptr, size_t n)
{
    size_t	nleft;
    ssize_t	nread;
    char	*ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR){
				 nread = 0;	 // 緩衝區滿了,忽略這個錯誤,繼續讀取
			}else{
				return(-1);	
			}  
        } else if (nread == 0)
            break;				/* EOF */

        nleft -= nread;
        ptr   += nread;
    }
    return(n - nleft);		/* return >= 0 */
}

ssize_t Readn(int fd, void *ptr, size_t nbytes)
{
    ssize_t		n;

    if ( (n = readn(fd, ptr, nbytes)) < 0){
        printf("readn error");
        exit(0);
    }

    return(n);
}



int main(void)
{
    int fd, n;
    char buffer[40];

    if ((fd= open("TEST.txt", O_RDONLY)) == -1)
    {
        printf("Error opening file.\n");
        exit(1);
    }

    n = Readn(fd, buffer,  sizeof(buffer));
    printf("read  %d bytes, content = %s\n", n, buffer);
    close(fd);
    return 0;
}

3、封裝read

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define MAXLINE 4096
static int	read_cnt;  // 靜態儲存區會自動初始化為0
static char	*read_ptr; // 靜態儲存區會自動初始化為NULL
static char	read_buf[MAXLINE];

static ssize_t Read(int fd, char *ptr)
{
    /*
     * read_cnt: 第一次呼叫時會初始化為0, 第一次呼叫完成之後先更新為從fd讀取到的字元數,然後自減少1
     *           第二次呼叫時為自減少的那個數
     * */
    int k = read_cnt;
    char	*qq = read_ptr;
    if (read_cnt <= 0) {  // read_cnt是read函式的返回值,返回值是正數的情況只有一種,那就是讀取成功
again:
        if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {   //如果在從fd讀取資料的時候出錯
            if (errno == EINTR){  //如果錯誤是緩衝區滿了,重新讀取
                goto again;
            }
            return(-1);
        } else if (read_cnt == 0) {// 如果讀到了檔案末尾, 直接返回
            return(0);
        }
        read_ptr = read_buf;  // read_ptr指向剛剛讀取的內容
    }
    /*
     * 此時: read_cnt更新為剛剛讀取到的字元個數
     * read_ptr和read_buf為剛剛讀取到的字串
     * */

    read_cnt--;
    *ptr = *read_ptr++;
    /*
     * 此時: read_cnt自減1
     * ptr指向read_ptr的第一個字元
     * read_ptr前移1個字元
    * */
    return(1);
}

ssize_t readline(int fd, void *vptr, size_t maxlen){
    ssize_t	n, rc;
    char	c, *ptr;

    ptr = vptr;
    for (n = 1; n < maxlen; n++) {  //
        if ( (rc = Read(fd, &c)) == 1) {   //c為有效字串, 'a', 'b', '\0'...
            *ptr++ = c;
            if (c == '\n')  // 如果提前讀取到了\n, 截斷
                break;	/* newline is stored, like fgets() */
        } else if (rc == 0) {
            *ptr = 0;
            return(n - 1);	/* EOF, n - 1 bytes were read */
        } else
            return(-1);		/* error, errno set by read() */
    }

    *ptr = 0;	/* null terminate like fgets() */
    return(n);
}

/*
 * 功能: 從fd中最多讀取maxlen個字元(包括\0)到ptr所指空間中
 *       如果fd中的字元大於maxlen,截斷
 *       如果fd中的字元小於maxlen,那就有多少就讀多少
 * 返回值: 返回讀取到的字元個數      
 * */
ssize_t Readline(int fd, void *ptr, size_t maxlen)
{
    ssize_t		n;

    if ( (n = readline(fd, ptr, maxlen)) < 0){
        printf("readline error");
        exit(0);
    }

    return(n);
}


int main(void)
{
    int fd, n;
    char buffer[3];

    if ((fd= open("TEST.txt", O_RDONLY)) == -1)
    {
        printf("Error opening file.\n");
        exit(1);
    }

    n = Readline(fd, buffer,  sizeof(buffer));
    printf("read  %d bytes, content = %s\n", n, buffer);
    close(fd);
    return 0;
}

上面的read、write的區別是一次讀取一個位元組,效率不太好。
改進如下(待補):