文本文件读写:read()、readline()、readlines()方法、write()与writelines()方法、flush()与缓冲区

文件读写,说白了就是让程序和硬盘上的文本文件「对话」。我刚开始学的时候,总觉得这玩意儿不就是打开、读、写、关闭嘛,有啥好讲的?直到我在项目中踩过几个坑,才发现里面的门道还真不少。

今天咱们就把文本文件的读写方法彻底捋一遍。你想想看,如果连文件都操作不明白,后面的数据持久化、日志记录、配置管理全都得抓瞎。

一、读取文件的三种姿势

Python 提供了三种读取文件的方法,各有各的脾气。我个人的习惯是:看文件大小选方法。

1. read() —— 一口吞

read() 方法会把文件内容一次性全部读出来,返回一个字符串。简单粗暴,但容易撑到。

with open('example.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print(content)
注意:如果文件很大(比如几百MB),千万别用 read()。我曾经在项目里处理一个 2GB 的日志文件,直接 read() 下去,内存爆了,程序挂了。嗯,那场面,至今难忘。

当然,你也可以给 read() 传一个数字参数,指定读取的字符数:

with open('example.txt', 'r', encoding='utf-8') as f:
    chunk = f.read(100)  # 只读前100个字符
    print(chunk)

2. readline() —— 一口一口吃

readline() 每次只读一行,包括行尾的换行符。适合逐行处理,内存友好。

with open('example.txt', 'r', encoding='utf-8') as f:
    line = f.readline()
    while line:
        print(line, end='')  # 去掉多余的换行
        line = f.readline()
小技巧:其实 Python 的 for line in f 就是基于 readline() 实现的,更简洁。我一般直接用 for 循环,除非需要手动控制读取进度。

3. readlines() —— 分盘装

readlines() 会把所有行读到一个列表里,每行是一个元素。适合行数不多、需要随机访问某一行的情况。

with open('example.txt', 'r', encoding='utf-8') as f:
    lines = f.readlines()
    for i, line in enumerate(lines):
        print(f"第{i+1}行: {line}", end='')
注意:readlines() 也会把整个文件加载到内存。如果文件有 100 万行,列表会占用大量内存。我建议:能逐行处理就别用 readlines()。

二、写入文件的两种方式

写文件比读文件稍微复杂一点,因为涉及到「覆盖」还是「追加」的问题。

1. write() —— 写字符串

write() 方法把字符串写入文件,不会自动加换行符。你需要手动加 \n。

with open('output.txt', 'w', encoding='utf-8') as f:
    f.write('第一行\n')
    f.write('第二行\n')
    f.write('第三行')
提示:模式 'w' 会覆盖原有内容,'a' 是追加。我经常在写日志时用 'a' 模式,这样不会丢失历史记录。

2. writelines() —— 写列表

writelines() 接收一个可迭代对象(比如列表),把每个元素写入文件。注意:它不会自动加换行符!

lines = ['第一行\n', '第二行\n', '第三行\n']
with open('output.txt', 'w', encoding='utf-8') as f:
    f.writelines(lines)
重点:writelines() 不会在元素之间加任何分隔符。如果你需要换行,必须在每个字符串末尾加上 \n。这是新手最容易犯的错误,我曾经也在这上面吃过亏。

三、flush() 与缓冲区

这个知识点,说实话,很多人学了 Python 好几年都没搞明白。但一旦你遇到「数据写进去了,文件里却没有」这种诡异问题,你就知道它有多重要了。

缓冲区是什么?

Python 写文件时,不会立刻把数据写到硬盘上。它会先攒在一个「缓冲区」里,等缓冲区满了或者文件关闭了,才一次性写入。这样做是为了提高性能——毕竟硬盘读写比内存慢得多。

# 看看缓冲区在干嘛
with open('test.txt', 'w', encoding='utf-8') as f:
    f.write('数据写进去了吗?')
    # 此时数据还在缓冲区,没到硬盘
    # 如果程序在这里崩溃,数据就丢了
    f.flush()  # 强制把缓冲区的内容写入硬盘
    # 现在数据安全了
避坑指南:我曾经写过一个实时日志系统,日志总是延迟几秒才出现在文件里。排查了半天,发现就是缓冲区在作怪。加上 flush() 后,问题立刻解决。

什么时候需要 flush()?

  • 实时监控日志:希望日志立刻写入文件,方便 tail -f 查看
  • 程序可能崩溃:在关键数据写入后立即 flush(),防止丢失
  • 交互式程序:用户输入后需要立即保存

缓冲区大小怎么调?

open() 函数的 buffering 参数可以控制缓冲区行为:

buffering 值 行为
-1 默认缓冲区大小(通常是 4096 或 8192 字节)
0 无缓冲(立刻写入,但性能差)
1 行缓冲(遇到换行符就写入)
>1 指定缓冲区大小(字节数)
# 行缓冲模式:每写一行就刷一次
with open('test.txt', 'w', buffering=1, encoding='utf-8') as f:
    f.write('第一行\n')  # 立刻写入
    f.write('第二行\n')  # 立刻写入

# 无缓冲模式:不推荐,除非你不在乎性能
with open('test.txt', 'w', buffering=0, encoding='utf-8') as f:
    f.write('立刻写入')
我的建议:日常开发用默认缓冲区就够了。只有在需要实时写入的场景下,才用 flush() 或行缓冲。别为了省事把所有文件都设成无缓冲,性能会差很多。

四、综合示例:一个简单的日志记录器

把今天学的知识串起来,写一个实用的日志记录器:

class SimpleLogger:
    def __init__(self, filename, buffering=1):
        self.filename = filename
        self.file = open(filename, 'a', buffering=buffering, encoding='utf-8')
    
    def log(self, message):
        from datetime import datetime
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        line = f'[{timestamp}] {message}\n'
        self.file.write(line)
        # 实时日志,立刻刷到硬盘
        self.file.flush()
    
    def close(self):
        self.file.close()

# 使用示例
logger = SimpleLogger('app.log')
logger.log('程序启动')
logger.log('用户登录成功')
logger.log('发生异常:文件未找到')
logger.close()

这个例子用到了 write()、flush()、行缓冲模式。你想想看,如果不用 flush(),程序突然崩溃,最后几条日志就丢了。嗯,这就是实战中必须考虑的问题。

总结一下

  • 读文件:小文件用 read(),大文件逐行用 for line in f,需要随机访问用 readlines()
  • 写文件:单行用 write(),批量用 writelines(),别忘了加 \n
  • 缓冲区:默认有缓冲,需要实时写入时用 flush() 或行缓冲

说白了,文件读写就是「内存和硬盘之间的搬运工」。你掌握了这些方法,以后处理配置文件、日志文件、数据导出,都能游刃有余。我当年要是有人这么给我讲,也不至于在缓冲区上栽跟头了。