6.6文件
阅读入口
目录与路径
目录树
文件系统是操作系统中负责管理持久化数据的子系统,它把存储设备上的数据组织成文件和目录,让程序不必关心数据落在哪个扇区。许多系统将路径组织成以目录为节点的层次结构:
- 根目录是树的起点。Windows中通常是盘符(如
C:\),Unix/Linux中是/。 - 目录是可以包含文件和其他目录的容器。
- 文件是存储数据的基本单元。
- 路径是定位文件的地址。
C:\ (根)├── Windows/├── Program Files/└── Users/ └── Alice/ ├── Documents/ │ ├── PythonProjects/ │ │ ├── ProjectA/ │ │ │ └── main.py │ │ └── DataAnalysis/ │ │ └── data.csv └── Downloads/Windows为每个盘符建立一棵独立的树,所以一台机器可以有多个根;Unix只在 / 处有一棵树,其他设备通过挂载接到树上。这个差别决定了路径怎么写,但两种系统里的目录都是树的节点。
路径
路径由沿途的目录名和最后的文件名拼成,分为两种:
| 类型 | 起点 | 例子 |
|---|---|---|
| 绝对路径 | 根目录 | C:\Users\Alice\Documents\file.txt |
| 相对路径 | 当前工作目录 | ./DataAnalysis/data.csv |
. 表示当前目录,.. 表示父目录。分隔符随系统不同:Windows用反斜杠 \,Unix用斜杠 /。在Python字符串里写Windows路径时,反斜杠要转义,或者改用原始字符串。
相对路径的起点是进程的当前工作目录,不是脚本文件所在的目录。同一个相对路径,从不同目录启动、或运行中切换了工作目录,就可能指向不同甚至不存在的文件。需要稳定定位时用绝对路径,或先根据脚本位置构造路径。
访问权限
名字之外,文件系统还为每个对象记录访问控制信息。Unix风格系统常用所有者、所属组和其他用户的读、写、执行权限位,也可能结合访问控制列表等机制。目录上的这三种权限含义与普通文件不同:
| 权限 | 对文件 | 对目录 |
|---|---|---|
| 读 | 读取内容 | 列出目录中的条目 |
| 写 | 修改内容 | 在目录中增删条目 |
| 执行 | 作为程序运行 | 进入该目录并访问其下的文件 |
Windows用访问控制列表(ACL)逐条记录允许和拒绝,概念上同样是“谁能对哪个对象做什么”。
打开文件时,模式说明这次要做什么,权限决定进程是否被允许这样做。因此打开失败不一定代表路径写错:文件不存在、路径中某一级目录不可进入、文件没有相应权限,都会让打开操作失败,区别只在错误码和异常类型。
Python的打开模式
Python的open使用模式字符串指定打开方式。以下基础模式互斥:
| 模式 | 文件已存在 | 文件不存在 |
|---|---|---|
r | 打开供读取 | 抛出异常 |
w | 打开时截断,供写入 | 创建文件 |
a | 写入追加到末尾 | 创建文件 |
x | 抛出异常,不覆盖 | 独占创建 |
+ 增加读写能力;b指二进制模式,t指文本模式,二者互斥。文本模式处理字符串与编码,二进制模式处理字节。模式决定请求的操作,实际能否打开仍取决于路径、权限等条件。
r+ 不会创建缺失文件;w+ 可以读写,但打开现有文件时仍会截断。a+ 支持读取和追加写入,写入仍发生在文件末尾;读取前按需要调整文件位置。
C的打开模式
C标准库的fopen用模式字符串指定文件流的访问方式。r打开已有文件供读取,w打开供写入并在文件存在时截断,a打开供追加写入;w与a在需要时创建文件。打开失败返回空指针,调用方应检查结果。
在基础模式中加入 + 可创建更新流,允许读取与写入;加入b表示二进制模式。文本流的外部表示可能与程序中的字符表示不同,实际转换取决于实现。C的标准模式集合与Python不完全相同,不能把Python的模式表直接当作C的规则。
更新流在切换读写方向时有次序要求:输出后转为输入,应先刷新或定位;输入后转为输出,应先定位,除非输入已遇到文件结尾。定位操作例如fseek;每次操作的返回值仍需检查。
追加模式的写入发生在文件末尾。对于追加更新流,初始读取位置可能由实现决定,跨平台代码应显式定位到希望读取的位置。
判断“用某个模式打开某个文件会怎样”,依据都在这张表里。下面的“判断顺序”把它压成四个依次回答的问题;路径部分按Windows写法阅读,两种语言的模式集合和文本处理规则分别判断。
两套接口
Python的文件对象
open(file, mode="r") 返回一个文件对象,mode中含b时是二进制文件对象,否则是文本文件对象。对象内部保存打开的文件描述符、缓冲区、当前位置和解码器,后续的读取、写入、定位都作用在这个对象上。
f = open("data.txt", "r")text = f.read()f.close()文本模式返回str,二进制模式返回bytes。文本模式的编码由encoding参数决定,省略时使用平台相关的默认编码。
C的流与FILE指针
C把文件抽象成流:一个有序的字节序列,附带缓冲区、当前位置、错误指示器和文件结束标志。FILE是保存这些状态的结构体,程序拿到的始终是FILE * 类型的文件指针。
FILE *fopen(const char *filename, const char *mode);int fclose(FILE *stream);fopen成功时返回文件指针,失败时返回NULL;fclose成功返回0,失败返回EOF。
关闭文件
关闭动作把缓冲区里尚未写出的数据刷到设备,并释放文件描述符。Python的close() 和C的fclose() 都承担这件事,区别在于语言是否替你保证调用:
- Python:用
with open(...) as f:时,离开代码块会自动调用close(),即使块内抛出异常也会执行。手写的f.close()在异常路径上容易被跳过。 - C:没有语言级的自动关闭,必须显式
fclose()。进程正常退出时标准库会刷新并关闭仍打开的流,但依赖这一点会掩盖错误,长期运行的程序还可能耗尽文件描述符。
Python的文件对象与C的FILE * 在这段里对照。阅读C接口时,把fopen的NULL和fclose的返回值一起记住。
学习安排
这一节从6.5进程 的“已打开文件表”接着往下:进程要读写持久化数据,得先把文件系统里的对象打开成本进程可用的句柄。教材的顺序是先用目录树和路径说明文件在哪里,再用一张模式表说明打开时能做什么,最后分别给出Python和C的接口。
建议按这个顺序读:先确认根目录、绝对路径与相对路径的差别,把“当前工作目录决定相对路径”这一点记住;再逐列读模式表,不要只背模式字符串;最后把两段Python示例与C的函数原型对照。C部分只列原型和返回值,没有完整程序,读的时候自己补一个fopen判空和fclose的骨架。
前面的语言基础在4.1 Python程序设计基础 和4.2 C程序设计基础。文件接口最终落到系统调用,6.4软件中断与系统调用 解释了fopen、open这些库函数下面发生了什么。
课程约定
- 目录树用Windows盘符和反斜杠举例,路径按Windows读;模式字符串按具体语言分别判断。
- Python的文本模式可用
t表示;C的标准模式不应直接套用Python的t写法。 - C11起可以用
x表示排他创建,也可以与w组合成wx;答题按题目给出的模式集合判断。 fclose成功返回0,失败返回EOF。- 示例文件名是
Task1.txt,路径写作./Task1.txt,程序从启动时的工作目录找它。
判断顺序
遇到“用某个模式打开某个文件会怎样”的问题,按固定顺序过一遍:
- 文件是否存在?不存在时看模式是否创建,
r系列直接报错。 - 存在时是否被清空?只有
w系列清空。 - 读写位置如何确定?追加模式的写入在末尾,更新流的初始读取位置及切换规则要结合语言和实现判断。
- 能不能读、能不能写?带
+才能同时读写。
按这四步走,r+ 与w+、a与a+ 两组就不会混。做题时常见两个选项分别问位置和读写能力,先定位再判断读写。
易错点
"w"的截断发生在打开时,哪怕一行都没写。"r+"不创建文件;要“存在就改、不存在就建”得自己判断,或改用会清空文件的"w+"。"a"只是追加写,"a+"才多了读;a+的写入位置仍在末尾,读之前通常要seek(0)。- Python的
readlines()读完后位置在文件末尾,紧接着的write是追加,不是覆盖。 close()会影响缓冲数据的写出与资源释放;Python使用with可以保证异常时也关闭文件。- C的
fopen必须判NULL,并注意更新流切换读写方向前需要定位或刷新。
复习要点
复习时把模式表的“文件存在时”一列遮住,只看模式和“文件不存在时”,逐个说出另一种情况;说全再反过来。这样能突出r+ 不会在文件不存在时创建文件这一差异。
写文件相关代码时先写open和close两行,再填中间的读写,避免最后忘记关闭。