当前位置: 首页 > news >正文

【Linux】聊聊文件那些事:从空文件占空间到系统调用怎么玩

前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

在这里插入图片描述


IF’Maxue:个人主页

 🔥 个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

文章目录

    • 1. 空文件(内容为0)也占磁盘空间吗?
    • 2. 文件存在哪?
    • 3. 谁在操作文件?
    • 4. 读/写文件的本质
    • 5. 举个例子
    • 6. 怎么用纯C把内容输出到显示器?
    • 7. `fwrite`函数怎么用?
    • 8. 键盘、显示器也是“文件
    • 9. `main`不是C程序的“第一站”?
    • 10. 打开文件的方式很关键
    • 11. 输出重定向(`>`)
    • 12. 写文件别带`\0`!
    • 13. 读文件比写难?用结构体就能“对得上”格式
    • 14. 直接调用操作系统的`open`:文件描述符和位图传参
      • 为什么用“位图传参”?`flags`的小技巧

1. 空文件(内容为0)也占磁盘空间吗?

你可能会想:如果我在电脑上建一个文件,里面什么都不写(内容是0),它是不是就像“空气”一样,不占磁盘空间?其实不是——因为文件不只是“里面写的内容”,还包含它的“身份信息”(属性) ,这俩加起来才是一个完整的文件。

就像你给朋友寄快递,快递盒里的东西是“内容”,但快递单上的收件人、地址、寄件时间、快递单号这些“信息”,也是快递的一部分,不能少。文件的属性也一样,比如文件名、创建时间、修改时间、权限、存储位置——这些属性不管文件内容是不是空的,都得存在磁盘上,操作系统要靠它们找到文件、判断能不能操作它。所以哪怕文件内容是0,属性也会占一点磁盘空间(比如Linux里空文件默认占4KB,因为磁盘按“块”存数据,最小块就是4KB)。

而且我们对文件的所有操作(读、写、复制),本质都是围绕“内容+属性”展开的。这一点从文件在磁盘的存储逻辑上也能直观看到,比如下面这张图就展示了文件内容与属性在磁盘中的关联关系:
文件存储结构示意图

2. 文件存在哪?

我们平时说的“文件”,默认都是存在磁盘里的——不管是机械硬盘、固态硬盘还是U盘,都算“永久性存储介质”(内存是临时存储,断电就没了,所以文件不能只存内存)。

但磁盘是“外部设备”,系统不能直接操作它,必须通过“输入输出操作”(简称“IO”)交换数据:比如读文件是“输入”(把磁盘内容读到内存),写文件是“输出”(把内存数据写到磁盘)。换句话说,所有对文件的操作(打开、读、写、关闭),本质都是系统和磁盘之间的“IO操作”——没有IO,就没法跟磁盘里的文件打交道。

3. 谁在操作文件?

你双击打开文件、用cat看文件内容,背后其实是“进程”在干活(比如记事本进程、cat进程)——系统里只有进程能发起操作,你点击鼠标、输命令,本质是让某个进程执行“操作文件”的任务。

那进程怎么操作文件?比如用C语言写fopen("test.txt", "r"),这是调用C标准库的“库函数”(fopenfclose这些),但库函数不是“最终执行者”——它是“中间人”,底层会调用操作系统的“系统调用” (比如Linux的openclose)。因为只有操作系统能直接指挥磁盘,不管你用C、Python还是Java,想操作文件,最后都得走系统调用的路子。

操作系统管理文件的核心逻辑是“先描述,再组织”:当进程打开文件时,操作系统不会把整个磁盘文件搬内存(太浪费),而是在内存建一个“文件描述结构”(比如Linux的struct file)——这就是“内存级文件”,记录文件当前读写位置、权限、磁盘位置等;而磁盘里的是“磁盘级文件”(原始文件)。操作系统会把所有“内存级文件”组织起来(比如用链表),方便管理。这个逻辑可以通过下面的示意图理解:
内存级文件与磁盘级文件关系

4. 读/写文件的本质

不管你用printf写显示器,还是用fwrite写文件,本质都不是靠库函数——库函数只是“打包”请求,最终要调用操作系统的“读/写系统调用”(比如Linux的readwrite)。因为读/写要跟外设打交道,只有操作系统能指挥外设。

而且有个铁律:访问文件必须先打开。打开文件的过程,是操作系统帮你做“准备工作”(查权限、建“内存级文件”、分配文件描述符),没准备好,后续读/写都没法做。

5. 举个例子

我们在Linux终端用cat 文件名看内容(比如cat test.txt),背后是完整的“打开→读→写→关闭”流程,正好能理解文件操作逻辑。

比如下面这张图,执行cat test.txt后,终端清晰显示了文件里的内容(比如“Hello, I’m a test file!”):
cat命令执行结果示例

这个过程的底层逻辑,在下面这张流程图里更直观:
cat命令操作流程

具体步骤是:

  1. 终端启动cat进程;
  2. cat调用open系统调用,以“只读”方式打开test.txt——操作系统查权限、建“内存级文件”、分配文件描述符(比如fd=3);
  3. cat调用read系统调用,通过fd=3读文件内容到内存;
  4. cat调用write系统调用,把内容写到“显示器”(显示器也是外设,对应文件描述符1);
  5. 读完后调用close关闭文件,释放资源。

6. 怎么用纯C把内容输出到显示器?

显示器是外设,但操作系统把它当成“文件”处理(统一接口,方便操作)。用纯C输出到显示器有好几种方式,下面这张图展示了最常用的两种:
纯C输出到显示器的代码示例

比如用printf

#include <stdio.h>
int main() {printf("Hello, this goes to screen!\n"); // 默认写往stdout(显示器)return 0;
}

或者用fwrite直接指定stdoutstdout是系统预定义的“标准输出”文件,对应显示器):

#include <stdio.h>
#include <string.h>
int main() {char buf[] = "Hi, fwrite to screen!\n";fwrite(buf, 1, strlen(buf), stdout); // 把buf写到stdoutreturn 0;
}

运行后,内容都会显示在显示器上——本质都是调用系统调用,把数据写到显示器对应的外设接口。

7. fwrite函数怎么用?

fwrite是C标准库的“写文件函数”,不仅能写磁盘文件,还能写显示器,下面这张图详细展示了它的用法和代码示例:
fwrite函数用法及代码示例

先看fwrite的函数原型:

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

四个参数的意思:

  • ptr:要写的内容地址(比如字符串、数组的地址);
  • size:每个元素的大小(比如字符是1字节,int是4字节);
  • nmemb:要写的元素个数;
  • stream:写的目标(可以是fopen返回的文件指针,也可以是stdout)。

比如写内容到test.txt的代码:

#include <stdio.h>
#include <string.h>
int main() {FILE *fp = fopen("test.txt", "w"); // 只写方式打开,不存在则新建if (fp == NULL) { // 必须判断打开是否成功(比如权限不够)perror("fopen error");return 1;}char content[] = "I'm written by fwrite!";// 写content到fp:1字节/元素,共strlen(content)个元素size_t write_num = fwrite(content, 1, strlen(content), fp);if (write_num != strlen(content)) {printf("Write failed! Only wrote %zu bytes\n", write_num);} else {printf("Write success!\n");}fclose(fp); // 一定要关闭文件,避免缓存数据没写到磁盘return 0;
}

运行后打开test.txt,就能看到写进去的内容;如果把fp换成stdout,内容就会显示在显示器上。

8. 键盘、显示器也是“文件

操作系统的重要设计:把所有能输入输出的东西都统一成“文件” ——磁盘文件、键盘、显示器、打印机,都用“打开→读→写→关闭”操作。这样进程不用记“读键盘用A函数,读磁盘用B函数”,只要记“读文件用read”就行。

Linux里每个进程启动时,系统会自动打开3个“标准文件”,不用手动fopen

  • stdin(标准输入):对应键盘,fd=0(scanffgets默认读这里);
  • stdout(标准输出):对应显示器,fd=1(printf默认写这里);
  • stderr(标准错误):对应显示器,fd=2(专门输出错误信息,比如perror)。

比如你用scanf("%d", &a),就是从stdin(键盘)读输入;用printf,就是往stdout(显示器)写内容——这三个文件是进程和用户交互的默认通道。

9. main不是C程序的“第一站”?

我们以为main是程序入口,但其实main是“我们写的代码的入口”——C程序启动前,需要初始化栈、全局变量,还要打开stdin/stdout/stderr,这些工作靠编译器和操作系统加的“启动代码”(比如Linux的crt0.o)完成。

整个流程是:

  1. 操作系统加载可执行文件,启动进程;
  2. 进程先执行“启动代码”:初始化环境、打开3个标准文件;
  3. 启动代码调用main,我们的代码才开始跑;
  4. main返回后,启动代码调用exit结束进程,释放资源。

所以main不是“第一站”——系统已经悄悄帮我们做好了所有准备,我们才能直接用printfscanf

10. 打开文件的方式很关键

fopen打开文件时,第二个参数是“打开方式”,比如"r"(只读)、"w"(只写)、"a"(追加),下面这张图展示了不同方式的效果差异:
文件打开方式(w/a)效果对比

  • "w"(只写):文件存在就清空内容,不存在就新建;
  • "a"(追加):文件存在就把指针移到末尾(写内容加在最后),不存在就新建;
  • "r"(只读):文件必须存在,否则打开失败,不会清空内容。

比如用"w"打开test.txt,原本的内容会被删掉;用"a"打开,新内容会跟在原有内容后面——这两种方式的选择直接影响文件操作结果,一定要注意。

11. 输出重定向(>

我们在终端用echo "hello" > test.txt,内容会写到test.txt而不是显示器,还会清空原有内容——为什么?因为重定向时,系统用了"w"方式打开文件。下面这张图解释了重定向的底层逻辑:
输出重定向(>)原理示意图

具体步骤:

  1. 启动echo进程;
  2. 看到>,系统把echostdout(原本对应显示器)改成test.txt
  3. 系统用"w"方式打开test.txt——存在就清空,不存在就新建;
  4. echo把“hello”写到stdout(现在对应test.txt);
  5. 进程退出,系统关闭文件。

如果不想清空,就用>>(追加重定向),这时系统用"a"方式打开文件,新内容会追加到末尾,下面这张图对比了>>>的效果:
>与>>重定向效果对比

12. 写文件别带\0

C语言字符串以\0结尾,但\0是C的“专属标记”,文件不认识它——写文件时带\0,会导致文件里多一个“空字符”,用cat看可能显示乱码(比如hello^@)。

下面这张图强调了“写文件别带\0”的注意事项和正确代码:
写文件避免带\0的代码示例

正确写法是用strlen控制写的长度(只写实际内容):

char str[] = "hello";
fwrite(str, 1, strlen(str), fp); // 写5个字节(不含\0)
// 错误写法:fwrite(str, 1, sizeof(str), fp); // 写6个字节(含\0)

记住:文件只存“实际内容”,不用带C语言的\0标记。

13. 读文件比写难?用结构体就能“对得上”格式

很多人觉得“读文件难”,是因为不知道“文件内容按什么格式存的”——比如写了“张三 20”,读的时候按“读整数→读字符串”就会错。解决办法是用“结构体”定义存储格式,写和读都按结构体来,就能“对得上”。

比如存“用户信息”(名字、年龄、身高),定义结构体struct User,写的时候按结构体写,读的时候也按结构体读——这样读出来的数据不会乱。

14. 直接调用操作系统的open:文件描述符和位图传参

fopen是库函数,底层调用操作系统的open系统调用——如果想“跳过中间人”,可以直接用open,它返回“文件描述符(fd)”(非负整数,0/1/2是stdin/stdout/stderr,新文件从3开始)。

下面这张图展示了open的函数原型、参数含义和代码示例:
open系统调用用法及代码示例

open的原型(Linux下):

// 新建文件时需要传权限,已有文件不用
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • pathname:文件名(比如"test.txt");
  • flags:打开标记(用位图传参,下面详细说);
  • mode:文件权限(比如0644,只有flagsO_CREAT时需要)。

比如新建并打开test.txt的代码:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {// 只写+新建+清空(O_WRONLY|O_CREAT|O_TRUNC),权限0644int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd == -1) { perror("open error"); return 1; }char buf[] = "Write via open!";write(fd, buf, strlen(buf)); // 用write系统调用写内容close(fd);return 0;
}

为什么用“位图传参”?flags的小技巧

flagsopen的关键参数,用“位图传参”——一个整数的每一位代表一个功能标记,比如第0位是“只读”,第2位是“新建”。这样做高效灵活:组合标记用“或(|)”,判断标记用“与(&)”。

下面这张图展示了常见flags的位图含义:
open函数flags位图传参说明

比如O_WRONLY | O_CREAT | O_TRUNC,就是“只写+新建+清空”的组合——对应的二进制位分别置1,操作系统通过判断这些位,就知道要执行哪些操作。位图传参不仅用在open,很多系统调用(比如fcntl)都用这种方式,是操作系统设计的常用技巧。


理解这些逻辑,再看各种语言的文件操作代码,就能从“记函数”变成“懂原理”——不管是C的fwrite、Python的open,还是Java的File,背后都是这套操作系统级的设计思路。

http://www.dtcms.com/a/388970.html

相关文章:

  • 基于代码层对运动台性能提升实战
  • openfeigin配置相关
  • 网络传输协议解析及SSE补充
  • 视觉SLAM第12讲:建图
  • 2025编程技术学习网站大全:从入门到精通的免费资源指南
  • 刷题日记0918
  • emacs 如何显示断点和运行的行标
  • 【c++】继承(2)
  • 大模型提示词Prompt工程:万能公式-完整指南
  • Flask RESTful API 教程:从零实现 Python CRUD 后端服务
  • 百年奢品家电ASKO亮相IFA2025|以至臻品质绘就生活新境
  • jvm排查full gc或者humongous obj思路?如何调优?
  • 实现.NetCore集成Serilog,写入日志文件,并按日期拆分文件夹
  • [新启航]航空发动机燃烧室喷嘴孔深光学 3D 轮廓测量 - 激光频率梳 3D 轮廓技术
  • iOS 上架 App 流程全解析 苹果应用发布步骤、App Store 审核流程、ipa 文件上传与 uni-app 打包实战经验
  • 22.6 单卡A100驯服30亿参数模型!DeepSpeed ZeRO-3实战显存优化指南
  • jvm垃圾搜集器
  • 小红书开放平台笔记详情接口实战:内容解析与数据挖掘全方案
  • App 上架平台全解析,iOS 应用发布流程、苹果 App Store 审核步骤
  • BeeWorks:私有化部署即时通讯,铸就企业数字安全基石
  • (数据分析方向)Flask 动漫数据可视化分析系统(Echarts + 番剧管理・大数据)(源码)✅
  • 2025 最新版 Node.js 下载安装及环境配置教程
  • 分布式流处理与消息传递——Kafka ISR(In-Sync Replicas)算法深度解析
  • JVM(三)-- 运行时数据区
  • 从比特币到Web3:数字资产犯罪的演进史
  • godot+c#实现状态机
  • linux计划任务管理
  • excel文件导入+存储过程导入表到业务表
  • Chromium 138 编译指南 macOS 篇:构建配置与编译优化(五)
  • 基于Java与Vue的MES生产制造管理系统,实现生产流程数字化管控,涵盖计划排程、质量追溯、设备监控等功能模块,提供完整源码支持二次开发,助力智能制造升级