news 2026/3/30 3:11:54

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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/lseekfopen/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。

#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>intopen(constchar*pathname,intflags,intmode);
  • 参数说明
    • pathname:文件路径/名称;
    • flags:打开模式(核心:O_RDONLY只读/O_WRONLY只写/O_RDWR读写,扩展:O_CREAT创建/O_TRUNC清空/O_APPEND追加);
    • mode:创建文件时的权限(如0666,表示所有用户可读可写)。

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

#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>#include<stdio.h>intmain(intargc,char**argv){// 以只写模式打开,不存在则创建,存在则清空,权限0666intfd=open("1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(-1==fd)// 错误处理:文件描述符返回-1表示失败{fprintf(stderr,"open error\n");return1;}return0;}

2.2 read/write:读写文件

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

(1)write:写入文件
ssize_twrite(intfd,constvoid*buf,size_tcount);
  • fd:目标文件描述符;
  • buf:待写入数据的缓冲区;
  • count:待写入的有效字节数;
  • 返回值:成功返回实际写入字节数,失败返回-1。

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

#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>#include<stdio.h>#include<unistd.h>#include<string.h>intmain(intargc,char**argv){intfd=open("1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(-1==fd){fprintf(stderr,"open error\n");return1;}charstr[100]="hello";// 关键:用strlen(str)而非sizeof(str),避免写入空字符ssize_tret=write(fd,str,strlen(str));printf("写入了%ld字节到文件",ret);// 输出:写入了5字节到文件close(fd);// 必须关闭文件,释放文件描述符return0;}
(2)read:读取文件
ssize_tread(intfd,void*buf,size_tcount);
  • fd:源文件描述符;
  • buf:接收数据的缓冲区;
  • count:缓冲区最大长度;
  • 返回值:>0实际读取字节数,==0文件末尾,<0读取失败。

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

#include<fcntl.h>#include<stdio.h>#include<unistd.h>#include<string.h>intmain(){intfd=open("/etc/passwd",O_RDONLY);// 只读打开系统文件if(-1==fd){fprintf(stderr,"open error\n");return1;}charbuf[50]={0};while(1){bzero(buf,sizeof(buf));// 清空缓冲区intret=read(fd,buf,sizeof(buf)-1);// 留1位存'\0'if(ret<=0){break;// 读取完毕或失败,退出循环}printf("{%d}:{%s}\n",ret,buf);// 打印读取长度和内容}close(fd);return0;}

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

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

#include<fcntl.h>#include<stdio.h>#include<sys/stat.h>#include<sys/types.h>#include<unistd.h>#include<string.h>intmain(intargc,char**argv){// 检查参数:需传入源文件和目标文件if(argc<3){printf("usage:./a.out srcfile dstfile\n");return1;}// 打开源文件(只读)和目标文件(只写/创建/清空)intfd_src=open(argv[1],O_RDONLY);intfd_dst=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);if(-1==fd_dst||-1==fd_src){fprintf(stderr,"open error\n");return1;}// 循环读写:1024字节缓冲区提升效率while(1){charbuffer[1024]={0};intret=read(fd_src,buffer,sizeof(buffer));if(ret<=0){break;}write(fd_dst,buffer,ret);// 按实际读取长度写入}// 关闭文件描述符close(fd_dst);close(fd_src);return0;}

2.4 lseek:调整文件偏移量

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

#include<fcntl.h>#include<stdio.h>#include<unistd.h>#include<string.h>intmain(){intfd=open("1.txt",O_RDWR);if(-1==fd){fprintf(stderr,"open error\n");return1;}// 方式1:获取文件大小(偏移到末尾,返回偏移量)longsize=lseek(fd,0,SEEK_END);printf("size %ld\n",size);// 方式2:创建空洞文件(偏移到1MB位置写入)lseek(fd,1024*1024,SEEK_SET);charstr[]="travel";write(fd,str,strlen(str));close(fd);return0;}

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

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

#include<fcntl.h>#include<stdio.h>#include<unistd.h>#include<string.h>#include<stdlib.h>intmain(intargc,char**argv){charbuf[10]={0};printf("pls input num:");fflush(stdout);// 刷新缓存,确保提示语先输出read(0,buf,sizeof(buf));// 从stdin(0)读取输入intnum=atoi(buf);// 转为整数write(2,&num,4);// 向stderr(2)写入整数(二进制)return0;}

三、标准IO(C库)实战

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

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

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

#include<stdio.h>#include<string.h>#include<stdlib.h>intmain(intargc,char**argv){FILE*fp=fopen("1.txt","r+");// 读写模式打开if(NULL==fp){fprintf(stderr,"fopen error\n");return1;}// 1. 获取文件大小:偏移到末尾,获取偏移量fseek(fp,0,SEEK_END);longsize=ftell(fp);printf("size is %ld\n",size);// 2. 准备插入内容:保存指定位置后的内容intpos=15;// 插入位置charinsert_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);return0;}

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

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

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"list.h"// 字典数据结构:单词+释义+链表节点typedefstruct{charword[50];charmean[512];structlist_headnode;}DATATYPE;// 添加单词到链表intadd_word(structlist_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);// 链表插入return0;}// 查找单词DATATYPE*find_word(structlist_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)){returnp;}}returnNULL;}intmain(intargc,char**argv){// 1. 打开字典文件FILE*fp=fopen("/home/linux/dict.txt","r");if(NULL==fp){fprintf(stderr,"open error\n");return1;}// 2. 初始化链表,读取字典文件structlist_headdict_head;INIT_LIST_HEAD(&dict_head);while(1){charstr[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){charwant_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);}}return0;}

四、核心总结

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避免缓冲区溢出)。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!