Unix/Linux编程:数据读写之write、fwrite、read、fread

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)
/*
* 作用: 从给定流 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函数的原型是

// (很少用了)
int read(int fd, char * buf, unsigned nbytes); 

有多种情况可以使实际读到的字节数少于要求读的字节数

  • 读取普通文件时,如果文件有30个字节,而要求读100个字节,则第一个read返回30.下一次调用read时将返回0
  • 读取终端设备时,最多一次读取一行
  • 读取网络时,网络中的缓冲机制可能造成返回值小于所要求读的字节数
  • 读取管道或者FIFO时,如果其中包含的字节少于所需数量,则read将只返回实际刻度字节数
  • 读取面向记录的设置比如磁带时,一次最多返回一个记录
  • 当一信号造成中断,而已经读了部分数据量时
#include <unistd.h>
/*
*功能: 从内存位置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)

对于普通文件,写操作从文件的当前偏移量处开始。如果在打开该文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数

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会替你做缓存,减少了实际出现的系统调用,所以效率比较高。

实践

一般用法

write/fwrite

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;
}

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);
}

read/wirte

1、一次一个字节从标准输入复制到标准输出

#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);
}

在这里插入图片描述
2、

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define	BUFFSIZE	4096

int main(void)
{
    int		n;
    char	buf[BUFFSIZE];

    while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
        if (write(STDOUT_FILENO, buf, n) != n){
            printf("write error");
            exit(0);
        }
            

    if (n < 0){
        printf("read error");
    }


// 进程终止时,会自动关闭输入/输出文件
    exit(0);
}

在这里插入图片描述
大多数文件系统采用了预读技术,当检测到正进行顺序读取时,系统就试图读入比应用所要求的更多数据,并假想应用很快就会去使用取这些数据

字节流套接字封装

字节流套接字(比如TCP套接字)上的read和write函数所表现的行为不同于通常的文件IO。字节流套接字上调用read或者write输入或者输出的字节数可能比请求的数量少,然而这不是出错的状态。这个现象的原因在于内核中用于套接字的缓冲区可能已经达到了极限。此时需要调用者再次调用read或者write函数,以输入或者输出剩余的字节。

封装write:Writen

#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 *ptr, size_t n)    /* Write "n" bytes to a descriptor  */
{
    size_t		nleft;
    ssize_t		nwritten;

    nleft = n;
    while (nleft > 0) {
        if ((nwritten = write(fd, ptr, nleft)) < 0) {
            if (nleft == n)
                return(-1); /* error, return -1 */
            else
                break;      /* error, return amount written so far */
        } else if (nwritten == 0) {
            break;
        }
        nleft -= nwritten;
        ptr   += nwritten;
    }
    return(n - nleft);      /* return >= 0 */
}
// 往一个描述符写n字节
void Writen(int fd, void *ptr, size_t nbytes)
{
    if (writen(fd, ptr, nbytes) != nbytes){
        printf("writen error");
        exit(0);
    }

}

封装read: Readn

#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 *ptr, size_t n)   /* Read "n" bytes from a descriptor  */
{
    size_t		nleft;
    ssize_t		nread;

    nleft = n;
    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (nleft == n)
                return(-1); /* error, return -1 */
            else
                break;      /* error, return amount read so far */
        } 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;
}

封装read: Readline

#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的区别是一次读取一个字节,效率不太好。
改进如下(待补):

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值