0


【Linux】文件操作函数 (详解)

📃个人主页:island1314

🔥个人专栏:Linux—登神长阶

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞


前言 🚀

🔥 每个系统都有自己的专属函数,我们习惯称其为系统函数。系统函数并不是内核函数,因为内核函数是不允许用户使用的,系统函数就充当了二者之间的桥梁,这样用户就可以间接的完成某些内核操作了。
如:open、close、lseek、read、write这些系统IO函数又被称为不带缓冲的IO (unbuffered IO)。术语不带缓冲指的是每个read和write都调用内核中的一个系统调用,因此也常叫做系统IO,与之相对应的还有标准IO(fopen、fread、fwrite、fclose等)。
应用层程序编写如下:

  1. 直接调用系统层接口IO(即open、 read、 write 等函数)
  2. 另一种则是调用后面C库的接口IO(即fopen、 fread、 fwrite 等 函数)间接地调用系统调用层接口。

**1. 系统 IO **

** 1.1 **open 函数 - 流打开

其实这个函数 我们之前在这篇博客里【Linux】基础 IO(文件描述符fd & 缓冲区 & 重定向)​​​​​​

提过的,大家可以去看看,以及下面的 close 和 write 也有说明

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> 
/*
 * @description   : 调用open可以打开或创建一个文件
 * @param - path  : 指定打开或创建的文件的文件名
 * @param - flags : 指定文件的打开方式,选项后面有说明
 * @param - mode  : 在创建新文件的时候才需要指定这个参数的值,用于指定新文件的权限(常用值0664==rw-rw-r--);
 * @return           : 若成功,返回内核分配的文件描述符(大于0),失败返回-1;
 */    
int open(const char *path, int flags, mode_t mode);

flags参数指定了文件打开方式等信息。用下列一个或多个宏常量进行“或 | ”运算构成flags参数:
(这些常量在头文件<fcntl.h>中定义)。
以下为必选属性 ,在这五个宏常量属性中必须指定一个且只能指定一个
flags说明O_RDONLY以只读方式打开文件O_WRONLY以只写方式打开文件O_RDWR.以读写方式打开文件O_EXEC只执行打开O_SEARCH只搜索打开(应用于目录)
以下为可选属性 , 可以和上边的属性一起使用。
flags说明O_APPEND新数据追加到文件尾部,不会覆盖文件的原来内容O_CREAT若此文件不存在则创建它,后面必须跟mode参数指定该新文件的访问权限。如果文件存在什么也不做

1.2 write 函数 - 流写入

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
//返回值:若成功,返回已写的字节数(带符号整型);若出错,返回-1
fd:指定需要偏移操作的文件描述符
buf:字符串
count:长度

返回值:通常与参数count的值相同,否则表示出错。
write 出错的一个常见原因:① 磁盘已写满,② 超过了一个给定进程的文件长度限制。

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

1.3 read 函数 - 流读取

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count); 
//返回值:若成功,返回读的字节数(带符号整型);若已到文件尾,返回0;出错,返回-1
fd:指定需要偏移操作的文件描述符
buf:字符串
count:长度

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

  1. 读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前有30个字节,而要求读100个字节,则read返回30。下一次再调用read时,它将返回 0 (文件尾端)
  2. 当从终端设备读时,通常一次最多读一行
  3. 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。
  4. 当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
  5. 当从某些面向记录的设备(如磁带〉读时,一次最多返回一个记录。
  6. 当一信号造成中断,而已经读了部分数据量时。读操从文件的当前偏移量处开始。在成功返问之前,该偏移量将增加实际读到的字节数。

1.4 close 函数 - 流关闭

#include <unistd.h>
/*
 * @description  : 调用close可以关闭一个已打开的文件
 * @param - fd     : 指定关闭的文件的描述符;
 * @return          : 若成功,返回0,失败返回-1;
 */    
int close(int fd);

关闭一个文件时还会释放该进程加在该文件上的所有记录锁。
当一个进程终止时,如果不调用close()手动关闭打开的文件,内核将自动关闭它所有的打开文件

1.5 lseek 函数 - 定位流

每个打开文件都有一个与其相关联的 “当前文件偏移量”
它通常是一个非负整数,用以度量从文件开始处计算的字节数。
通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。

  • 按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0

当然也可以调用 lseek 显式地为一个打开文件设置偏移量。

#include <sys/types.h>
#include <unistd.h>
/*
 * @description    : 调用lseek函数可以移动文件指针,也可以通过这个函数进行文件的拓展。
 * @param - fd       : 指定需要偏移操作的文件描述符;
 * @param - offset : 指定偏移量,需要和第三个参数配合使用
 * @param - whence : 通过这个参数指定函数实现什么样的功能:
 *                 SEEK_SET: 从文件头部开始偏移 offset 个字节
 *                 SEEK_CUR: 从当前文件指针的位置向后偏移 offset 个字节
 *                 SEEK_END: 从文件尾部向后偏移 offset 个字节
 * @return            : 若成功,返回文件指针从头部开始计算总的偏移量;出错,返回-1
 */    
off_t lseek(int fd, off_t offset, int whence);

对参数offset的解释与参数whence的值(符号常量)有关:

  1. 若whence是 SEEK_SET,则将该文件的偏移量设置为距文件开始处 offset个字节
  2. 若whence是 SEEK_CUR,则将该文件的偏移量设置为其当前值加 offset,offset可为正或负
  3. 若whence是 SEEK_END,则将该文件的偏移量设置为文件长度加 offset,offset可为正或负

1.6 综合样例

使用代码打开当前路径下的“bite”文件(如果文件不存在在创建文件),向文件当中写入“i like linux!”.

#include <stdio.h>
#include <unistd.h>//是close, write这些接口的头文件
#include <string.h>
#include <fcntl.h>//是 O_CREAT 这些宏的头文件
#include <sys/stat.h>//umask接口头文件

int main()
{
    //将当前进程的默认文件创建权限掩码设置为0--- 并不影响系统的掩码,仅在当前进程内生效
    umask(0);
    //int open(const char *pathname, int flags, mode_t mode);
    int fd = open("./bite", O_CREAT|O_RDWR, 0664);
    if(fd < 0) {
        perror("open error");
        return -1; 
    }   
    char *data = "i like linux\n!";
    //ssize_t write(int fd, const void *buf, size_t count);
    ssize_t ret = write(fd, data, strlen(data));
    if (ret < 0) {
        perror("write error");
        return -1; 
    }   
    //off_t lseek(int fd, off_t offset, int whence);
    lseek(fd, 0, SEEK_SET);
    char buf[1024] = {0};
    //ssize_t read(int fd, void *buf, size_t count);
    ret = read(fd, buf, 1023);
    if (ret < 0) {
        perror("read error");
        return -1; 
    }else if (ret == 0) {
        printf("end of file!\n");
        return -1; 
    }   
    printf("%s", buf);
    close(fd);
    return 0;
}

运行结果如下:

**2. 标准 IO **

**2.1 **fopen 函数 - 流打开

#incldue<stdio.h> 
FILE * fopen(const char *pathname, const char *method);

功能:用于打开文件

参数:

  1. pathname:被打开文件的文件路径以及文件名。
  2. method:打开文件的方式。

具体方式如下:

  1. “r" 或 ”rb" 以只读方式打开文件。
  2. “w" 或 ”wb" 以写方式打开文件,新内容会覆盖原本内容。
  3. “a” 或 “ab” 以写方式打开文件,新内容追加在文件末尾。
 FILE *fp;//定义一个指向FILE结构的指针
   // 在当前路径用可读可写打开一个“ccc.txt”的文件,如果不存在则创建它
    fp=fopen("./ccc.txt","w+");

返回值

  • 成功:它返回一个指向FILE结构的指针,该结构代表这个新创建的流(文件顺利打开后,指向该流的文件指针就会被返回)
  • 失败:它就会返回一个空指针,errno会提示问题的性质(如果文件打开失败,则返回NULL,并把错误代码存在errno中)

当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针( 文件指针)。
该对象通常是一个结构,它包含了标准IO库为管理该流需要的所有信息(流),
包括用于实际IO 的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。

  • 为了引用一个流,需将 FILE 指针作为参数传递给每个标准IO函数*

2.2 fwrite 函数 - 流写入

size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);

参数:

  1. ptr – 是一个指针,对fwrite来说,是要输出数据的地址
  2. size – 这是要读取的每个元素的大小,以字节为单位
  3. nmemb – 这是元素的个数,每个元素的大小为 size 字节
  4. stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流

返回值

  • 如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。

2.3 fread 函数 - 流读取

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

参数:

  1. ptr – 是一个指针,对fread来说,它是读入数据的存放地址。
  2. size – 这是要读取的每个元素的大小,以字节为单位。
  3. nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  4. stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

返回值

  • 成功读取的元素总数会以 size_t nmemb对象返回,size_t 对象是一个整型数据类型。
  • 如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾

2.4 fclose 函数 - 流关闭

int fclose( FILE *file )

返回值:

  • 对于输出流,fclose函数会在文件关闭前刷新缓冲区,
  • 如果它执行成功,fclose返回0

在该文件被关闭之前,冲洗缓冲中的输出数据。

  • 缓冲区中的任何输入数据被丢弃。如果标准I/O库已经为该流自动分配了一个缓冲区,则释放此缓冲区。
  • 当一个进程正常终止时(直接调用exit函数,或从main函数返回),

则所有带未写缓冲数据的标准IO流都被冲洗,所有打开的标准IO流都被关闭

2.5 fseek 函数 - 定位流

int fseek(FILE *stream, long offset, int whence);

参数:

  1. stream: 文件指针。
  2. offset: 偏移量,就是相当于当前位置,向左(右)移动几位。正数表示右向偏移,负数表示左向偏移。
  3. whence: 定义文件中哪里开始偏移,取值可为:SEEK_CUR(当前位置)、 SEEK_END (文件结尾)或 SEEK_SET(文件开头)。
  • 其中SEEK_SET,SEEK_CUR和SEEK_END依次为0,1和2。

返回值:

  • 如果执行成功,函数返回0。如果执行失败,函数返回一个非0值。

2.6 综合样例

使用代码打开当前路径下的“bite”文件(如果文件不存在在创建文件),向文件当中写入“linux so easy!”.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    FILE *fp = fopen("./byte", "wb+");
    if(fp == NULL){ 
        perror("fopen Error");
        return -1;
    }
    fseek(fp, 0, SEEK_SET);//跳转读写位置到,从文件起始位置开始偏移0个字节
    char *data = "linux so easy!\n";
    size_t ret = fwrite(data, 1, strlen(data), fp);
    if(ret != strlen(data)){
        perror("fwrite Error");
        return -1;
    }
    fseek(fp, 0, SEEK_SET);
    char buf[1024] = {0};
    //因为设置读取块大小位1,块个数为1023因此fread返回值为实际读取到的数据长度
    ret = fread(buf, 1, 1023, fp);
    if (ret == 0) {
        if (ferror(fp)) //判断上一次IO操作是否正确
            printf("fread error\n");
        if (feof(fp)) //判断是否读取到了文件末尾
            printf("read end of file!\n");
        return -1; 
    }   
    printf("%s", buf);
    fclose(fp);
    return 0;
}

运行结果如下:

3. 区别 🔖

(1)缓冲机制

  • 系统 I/O:- 通常不使用缓冲,直接进行数据传输,这可能导致性能较低,因为每个 I/O 操作都涉及系统调用。
  • 标准 I/O:- 采用缓冲机制,能提高 I/O 性能,尤其是在频繁读取或写入时。标准 I/O 会将数据存储在内存中,减少对系统调用的直接需求。

(2)灵活性和可移植性

  • 系统 I/O:- 通常依赖于特定操作系统的实现,可能不具有跨平台的可移植性。
  • 标准 I/O:- 由 C 标准库定义,具有较高的可移植性,可以在不同的平台上使用相同的代码。

(3)错误处理

  • 系统 I/O:- 需要手动检查返回值和设置 errno 来处理错误。
  • 标准 I/O:- 提供了更方便的错误处理机制,可以使用 ferror() 等函数检查错误状态。

4. 总结 📖

  • 使用场景: 如果需要底层控制和优化性能,可以使用系统 I/O;如果希望简化开发过程,使用标准 I/O 更为合适。
  • 复杂性: 标准 I/O 提供更高的抽象和易用性,适合大多数常规应用;系统 I/O 则适用于对性能和资源管理有特殊要求的场合。

★,°*:.☆( ̄▽ ̄)/$:*.°★ 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!**

标签: linux 运维 IO

本文转载自: https://blog.csdn.net/island1314/article/details/143312866
版权归原作者 IsLand1314~ 所有, 如有侵权,请联系我们删除。

“【Linux】文件操作函数 (详解)”的评论:

还没有评论