28、Linux文件IO与标准IO详解:从概念到实战

Linux文件IO与标准IO详解:从概念到实战

一、核心概念:文件IO vs 标准IO

1.1 基本定义

  • 文件IO(系统调用) :操作系统为用户操作文件提供的底层系统函数(如open/read/write/close),直接和内核交互,也叫「低级IO」。
  • 标准IO(C库函数) :C语言标准库封装的文件操作函数(如fopen/fread/fwrite/fclose),底层调用文件IO,跨平台性更强,也叫「高级IO」。

1.2 底层关系与核心区别

C库函数是对系统调用的封装:标准IO为了提升效率增加了用户态缓存,而文件IO无缓存(直接操作内核态),两者的核心区别可总结为:

特性 文件IO(系统调用) 标准IO(C库)
操作接口 open/read/write/close/lseek fopen/fread/fwrite/fclose/fseek
标识类型 文件描述符(int,如0/1/2分别对应stdin/stdout/stderr) 文件流指针(FILE*)
缓存机制 无缓存(直接操作内核) 带用户态缓存(减少系统调用次数)
跨平台性 依赖操作系统(如Linux特有) 跨平台(符合C标准)
适用场景 设备文件、实时性要求高的场景 普通文件、追求效率的通用场景
权限控制 打开时直接指定(如0666) 依赖fopen模式(如"r+")

二、文件IO(系统调用)实战

文件IO是Linux内核提供的底层接口,无缓存、功能强大,适合对设备/实时性要求高的场景,核心函数包括open/read/write/lseek/close

2.1 open:打开/创建文件

open函数用于打开或创建文件,返回唯一的文件描述符(fd),失败返回-1。

c 复制代码
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int open(const char *pathname, int flags, int mode);
  • 参数说明
    • pathname:文件路径/名称;
    • flags:打开模式(核心:O_RDONLY只读/O_WRONLY只写/O_RDWR读写,扩展:O_CREAT创建/O_TRUNC清空/O_APPEND追加);
    • mode:创建文件时的权限(如0666,表示所有用户可读可写)。

实战示例(01open.c):创建并清空1.txt文件

c 复制代码
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    // 以只写模式打开,不存在则创建,存在则清空,权限0666
    int fd = open("1.txt",  O_WRONLY| O_CREAT|O_TRUNC,0666);
    if(-1 == fd) // 错误处理:文件描述符返回-1表示失败
    {
        fprintf(stderr,"open error\n");
        return 1;
    }
    return 0;
}

2.2 read/write:读写文件

文件IO的读写通过read(读)和write(写)实现,直接操作字节流,无缓存。

(1)write:写入文件
c 复制代码
ssize_t write(int fd, const void *buf, size_t count);
  • fd:目标文件描述符;
  • buf:待写入数据的缓冲区;
  • count:待写入的有效字节数;
  • 返回值:成功返回实际写入字节数,失败返回-1。

实战示例(04write.c):向1.txt写入"hello"

c 复制代码
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv)
{
    int fd = open("1.txt",  O_WRONLY| O_CREAT|O_TRUNC,0666);
    if(-1 == fd)
    {
        fprintf(stderr,"open error\n");
        return 1;
    }
    char str[100]="hello";
    // 关键:用strlen(str)而非sizeof(str),避免写入空字符
    ssize_t ret = write(fd,str,strlen(str));
    printf("写入了%ld字节到文件",ret); // 输出:写入了5字节到文件
    close(fd); // 必须关闭文件,释放文件描述符
    return 0;
}
(2)read:读取文件
c 复制代码
ssize_t read(int fd, void *buf, size_t count);
  • fd:源文件描述符;
  • buf:接收数据的缓冲区;
  • count:缓冲区最大长度;
  • 返回值:>0实际读取字节数,==0文件末尾,<0读取失败。

实战示例(05read.c):读取/etc/passwd文件内容

c 复制代码
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("/etc/passwd", O_RDONLY); // 只读打开系统文件
    if (-1 == fd)
    {
        fprintf(stderr, "open error\n");
        return 1;
    }
    char buf[50] = {0};
    while (1) {
        bzero(buf, sizeof(buf)); // 清空缓冲区
        int ret = read(fd, buf, sizeof(buf)-1); // 留1位存'\0'
        if (ret <= 0) {
            break; // 读取完毕或失败,退出循环
        }
        printf("{%d}:{%s}\n",ret,buf); // 打印读取长度和内容
    }
    close(fd);
    return 0;
}

2.3 实战案例:文件拷贝(06readcp.c)

结合read/write实现简易版cp命令,核心逻辑是「循环读取源文件→写入目标文件」:

c 复制代码
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

int main(int argc ,char **argv)
{
    // 检查参数:需传入源文件和目标文件
    if (argc < 3)
    {
        printf("usage:./a.out srcfile dstfile\n");
        return 1;
    }
    // 打开源文件(只读)和目标文件(只写/创建/清空)
    int fd_src = open(argv[1], O_RDONLY);
    int fd_dst = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC,0666);

    if (-1 == fd_dst || -1 ==fd_src)
    {
        fprintf(stderr, "open error\n");
        return 1;
    }
    // 循环读写:1024字节缓冲区提升效率
    while (1) {
        char buffer[1024] = {0};
        int ret = read(fd_src, buffer, sizeof(buffer));
        if (ret <= 0)
        {
            break;
        }
        write(fd_dst, buffer, ret); // 按实际读取长度写入
    }
    // 关闭文件描述符
    close(fd_dst);
    close(fd_src);
    return 0;
}

2.4 lseek:调整文件偏移量

lseek用于移动文件读写指针,可实现「获取文件大小」「空洞文件创建」等功能:

c 复制代码
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("1.txt", O_RDWR);
    if (-1 == fd)
    {
        fprintf(stderr, "open error\n");
        return 1;
    }
    // 方式1:获取文件大小(偏移到末尾,返回偏移量)
    long size = lseek(fd, 0, SEEK_END);
    printf("size %ld\n",size);

    // 方式2:创建空洞文件(偏移到1MB位置写入)
    lseek(fd, 1024*1024, SEEK_SET);
    char str[]="travel";
    write(fd,str,strlen(str));
    close(fd);
    return 0;
}

2.5 标准输入输出(07stdin.c)

Linux中0/1/2分别对应标准输入(stdin)、标准输出(stdout)、标准错误(stderr),可直接通过文件描述符操作:

c 复制代码
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int	main(int argc, char **argv)
{
    char buf[10]={0};
    printf("pls input num:");
    fflush(stdout); // 刷新缓存,确保提示语先输出
    read(0,buf,sizeof(buf)); // 从stdin(0)读取输入
    int num =atoi(buf); // 转为整数
    write(2,&num,4); // 向stderr(2)写入整数(二进制)
    return 0;
}

三、标准IO(C库)实战

标准IO是C库对文件IO的封装,自带缓存机制,跨平台性更好,核心函数包括fopen/fread/fwrite/fseek/ftell

3.1 核心函数:文件读写与偏移

标准IO通过FILE*指针标识文件,结合fseek/ftell可轻松实现「文件插入内容」(02insert.c):

c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int	main(int argc, char **argv)
{
    FILE*fp = fopen("1.txt","r+"); // 读写模式打开
    if(NULL == fp)
    {
        fprintf(stderr,"fopen error\n");
        return 1;
    }

    // 1. 获取文件大小:偏移到末尾,获取偏移量
    fseek(fp,0,SEEK_END);
    long size = ftell(fp);
    printf("size is %ld\n",size);

    // 2. 准备插入内容:保存指定位置后的内容
    int pos = 15; // 插入位置
    char insert_str[100]="hello";
    fseek(fp,pos,SEEK_SET); // 偏移到插入位置
    char * data =(char*)malloc(size); // 分配内存保存后半部分
    fread(data,size-pos,1,fp); // 读取后半部分内容

    // 3. 插入内容:覆盖写入新内容+原有后半部分
    fseek(fp,pos,SEEK_SET);
    fwrite(insert_str, strlen(insert_str), 1, fp); // 写入插入内容
    fwrite(data, size-pos, 1, fp); // 写入原有后半部分

    // 4. 释放资源
    fclose(fp);
    free(data);
    return 0;
}

3.2 实战案例:字典查询(03dict.c)

结合标准IO的fgets读取文件、链表存储数据,实现简易字典查询功能:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"

// 字典数据结构:单词+释义+链表节点
typedef struct
{
    char word[50];
    char mean[512];
    struct list_head node;
} DATATYPE;

// 添加单词到链表
int add_word(struct list_head* head, char* word, char* mean)
{
    DATATYPE* p = (DATATYPE*)malloc(sizeof(DATATYPE));
    if (NULL == p)
    {
        fprintf(stderr, "add malloc error\n");
        return -1;
    }
    strcpy(p->word, word);
    strcpy(p->mean, mean);
    list_add(&p->node, head); // 链表插入
    return 0;
}

// 查找单词
DATATYPE* find_word(struct list_head* head, char* want_word)
{
    DATATYPE* p = NULL;
    DATATYPE* n = NULL;
    // 安全遍历链表
    list_for_each_entry_safe(p, n, head, node)
    {
        if (0 == strcmp(want_word, p->word))
        {
            return p;
        }
    }
    return NULL;
}

int main(int argc, char** argv)
{
    // 1. 打开字典文件
    FILE* fp = fopen("/home/linux/dict.txt", "r");
    if (NULL == fp)
    {
        fprintf(stderr, "open error\n");
        return 1;
    }

    // 2. 初始化链表,读取字典文件
    struct list_head dict_head;
    INIT_LIST_HEAD(&dict_head);
    while (1)
    {
        char str[1024] = {0};
        if (NULL == fgets(str, sizeof(str), fp)) // 逐行读取
        {
            break;
        }
        // 分割单词和释义
        char* word = strtok(str, " ");
        char* mean = strtok(NULL, "\r");
        add_word(&dict_head, word, mean); // 添加到链表
    }

    // 3. 交互查询单词
    while (1)
    {
        char want_word[50] = {0};
        printf("pls input want_word:");
        fgets(want_word, sizeof(want_word), stdin); // 读取用户输入
        want_word[strlen(want_word) - 1] = '\0'; // 去掉换行符
        if(0 == strcmp(want_word,"#quit")) // 退出条件
        {
            break;
        }
        // 查找并输出结果
        DATATYPE* tmp = find_word(&dict_head,want_word);
        if(NULL == tmp)
        {
            printf("cant find word:%s\n",want_word);
        }
        else  
        {
            printf("word:%s mean:%s\n",tmp->word,tmp->mean);
        }
    }
    return 0;
}

四、核心总结

4.1 关键结论

  1. 底层关系:标准IO是文件IO的封装,跨平台优先选标准IO,设备/实时场景选文件IO;
  2. 缓存影响:标准IO的缓存可减少系统调用次数(提升效率),文件IO无缓存(实时性更高);
  3. 标识差异:文件IO用文件描述符(int),标准IO用文件流指针(FILE*);
  4. 错误处理:文件IO返回-1表示失败,标准IO返回NULL(如fopen),需严格校验返回值。

4.2 选型建议

  • 操作普通文件(如txt、配置文件):优先标准IO(fopen/fread/fwrite);
  • 操作设备文件(如串口、网卡):优先文件IO(open/read/write);
  • 需要跨平台:必须用标准IO;
  • 需要精准控制文件偏移/权限:用文件IO。

五、常见问题与注意事项

  1. 文件权限:open的mode参数(如0666)最终权限会受umask影响(实际权限=mode & ~umask);
  2. 缓存问题:标准IO需注意fflush刷新缓存,避免数据未写入磁盘;
  3. 内存泄漏:标准IO的malloc内存、文件描述符/流指针必须手动释放/关闭;
  4. 参数校验:read/write的count参数需合理(如read用sizeof(buf)-1避免缓冲区溢出)。
相关推荐
啊森要自信7 小时前
CANN ops-cv:AI 硬件端视觉算法推理训练的算子性能调优与实战应用详解
人工智能·算法·cann
cur1es8 小时前
【UDP的报文结构】
网络·网络协议·udp·md5
仟濹8 小时前
算法打卡day2 (2026-02-07 周五) | 算法: DFS | 3_卡码网99_计数孤岛_DFS
算法·深度优先
驭渊的小故事8 小时前
简单模板笔记
数据结构·笔记·算法
开开心心就好8 小时前
发票合并打印工具,多页布局设置实时预览
linux·运维·服务器·windows·pdf·harmonyos·1024程序员节
惊讶的猫8 小时前
OpenFeign(声明式HTTP客户端)
网络·网络协议·http·微服务·openfeign
YuTaoShao8 小时前
【LeetCode 每日一题】1653. 使字符串平衡的最少删除次数——(解法一)前后缀分解
算法·leetcode·职场和发展
VT.馒头8 小时前
【力扣】2727. 判断对象是否为空
javascript·数据结构·算法·leetcode·职场和发展
goodluckyaa8 小时前
LCR 006. 两数之和 II - 输入有序数组
算法
孤狼warrior8 小时前
YOLO目标检测 一千字解析yolo最初的摸样 模型下载,数据集构建及模型训练代码
人工智能·python·深度学习·算法·yolo·目标检测·目标跟踪