文件指针机制与分块读取
摘要
文件指针是顺序移动的,读或写都会推进它。在同一个打开的文件对象上执行多次读取前,必须用 seek(0) 将指针复位;否则从当前位置读起,只能读到剩余内容,乃至空内容。
核心概念:文件指针
文件对象内部维护着一个文件指针,指向当前读写位置。所有读写操作都从这个位置开始,操作完成后指针自动向后移动。
指针行为示例
f = open('example.txt', 'r', encoding='utf-8')
# 第一次 read():从位置 0 读到末尾,指针移至 EOF
content1 = f.read()
print(f"读完后指针位置:{f.tell()}") # "Hello\nWorld" 共 11 字节,输出 11
# 第二次调用:从当前位置(EOF)读起,已无内容
content2 = f.readlines()
print(content2) # []
f.close()
图示
初始状态:
[H][e][l][l][o][\n][W][o][r][l][d][EOF]
↑ (位置 0)
read() 后,指针移至末尾:
[H][e][l][l][o][\n][W][o][r][l][d][EOF]
↑ (位置 11)
此时再调用 readlines(),从位置 11 读起,后面只有 EOF,返回 []。
解决方案:seek(0) 重置指针
f = open('example.txt', 'r', encoding='utf-8')
all_content = f.read()
print(f"内容长度:{len(all_content)}")
f.seek(0) # 重置指针到开头
lines = f.readlines()
print(f"行数:{len(lines)}")
f.close()
各方法对指针的影响
假设文件内容为 AB\nCD\nEF\n(共 9 字节):
| 操作 | 指针起始 | 指针结束 | 读取结果 |
f.read() | 0 | 9(EOF) | 全部内容 |
f.read(2) | 0 | 2 | "AB" |
f.readline() | 0 | 3 | "AB\n" |
f.readlines() | 0 | 9(EOF) | ["AB\n", "CD\n", "EF\n"] |
for line in f: | 0 | 9(EOF) | 逐行迭代 |
f.write("X") | 当前位置 | 当前位置 + 1 | 写入并移动 |
常见陷阱
陷阱 1:混用 read() 与迭代器
with open('data.txt', 'r') as f:
header = f.read(10) # 读前 10 字节(文件头信息)
for line in f: # 从第 10 字节处开始,可能切断某行的中间
print(line) # 第一行可能是残缺的半行
陷阱 2:写入后读取
with open('test.txt', 'w+') as f: # w+ 先清空文件
f.write('Hello') # 写入后指针在位置 5
content = f.read() # 从位置 5 读,为空
print(content) # ''
f.seek(0)
content = f.read() # 正确:'Hello'
陷阱 3:追加模式下的指针
with open('log.txt', 'a+') as f: # a+ 打开后指针在末尾
f.write('New line\n') # 写入成功
f.read() # 从末尾读,为空
f.seek(0)
all_logs = f.read() # 正确:读到全部内容
最佳实践:安全地多次读取
方法 1:多次打开(推荐)
with open('data.txt', 'r') as f:
content = f.read()
with open('data.txt', 'r') as f:
lines = f.readlines()
# 每次 with 块都是新指针,互不干扰
方法 2:seek(0) 重置
with open('data.txt', 'r') as f:
data = f.read()
size = len(data)
f.seek(0)
for line in f:
process(line)
方法 3:一次读入,内存操作
with open('data.txt', 'r') as f:
content = f.read()
lines = content.splitlines() # 不涉及指针,在内存中操作
words = content.split()
底层原理简述
Python 的文件对象封装了 C 标准库的 FILE* 结构,其中包含:
-
文件描述符(fd):操作系统层面的文件句柄
-
缓冲区:减少系统调用的内存缓存
-
文件位置标记:即本文所说的"指针",记录下次读写的字节偏移量
调用 read() 时,Python 请求操作系统内核推进文件偏移量。seek() 直接调整这个偏移量,tell() 查询其当前值。