文章
合集Python 语言基础第 19 / 21 篇

文件指针机制与分块读取

摘要

文件指针是顺序移动的,读或写都会推进它。在同一个打开的文件对象上执行多次读取前,必须用 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()09(EOF)全部内容
f.read(2)02"AB"
f.readline()03"AB\n"
f.readlines()09(EOF)["AB\n", "CD\n", "EF\n"]
for line in f:09(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() 查询其当前值。