Python学习历程——文件

Python学习历程------文件

  • 概述
  • 一、文件操作基础
    • [1.1 `open()` 函数:打开文件](#1.1 open() 函数:打开文件)
      • [1. 基本语法:`open(file, mode='r', encoding=None)`](#1. 基本语法:open(file, mode='r', encoding=None))
      • [2. 关键参数:`file` (文件名/路径), `mode` (模式), `encoding` (编码)](#2. 关键参数:file (文件名/路径), mode (模式), encoding (编码))
      • [3. 与Java的对比](#3. 与Java的对比)
    • [1.2 读取文件内容](#1.2 读取文件内容)
      • [1. `read(size)`:读取指定字节数或全部内容](#1. read(size):读取指定字节数或全部内容)
      • [2. `readline()`:读取一行内容](#2. readline():读取一行内容)
      • [3. `readlines()`:读取所有行并返回一个列表](#3. readlines():读取所有行并返回一个列表)
      • [4. 迭代文件对象:`for line in file:` (内存效率最高的方式)](#4. 迭代文件对象:for line in file: (内存效率最高的方式))
    • [1.3 写入文件内容](#1.3 写入文件内容)
      • [1. `write(string)`:将字符串写入文件](#1. write(string):将字符串写入文件)
      • [2. `writelines(list_of_strings)`:将一个字符串列表写入文件](#2. writelines(list_of_strings):将一个字符串列表写入文件)
    • [1.4 关闭文件](#1.4 关闭文件)
  • 二、文件模式与指针控制
    • [2.1 文件访问模式](#2.1 文件访问模式)
      • [1. `r`:只读 (默认)](#1. r:只读 (默认))
      • [2. `w`:写入 (文件存在则清空,不存在则创建)](#2. w:写入 (文件存在则清空,不存在则创建))
      • [3. `a`:追加 (在文件末尾写入,不存在则创建)](#3. a:追加 (在文件末尾写入,不存在则创建))
      • [4. `x`:独占创建 (文件已存在则报错)](#4. x:独占创建 (文件已存在则报错))
      • [5. `b`:二进制模式 (用于图片、视频等非文本文件)](#5. b:二进制模式 (用于图片、视频等非文本文件))
      • [6. `t`:文本模式 (默认)](#6. t:文本模式 (默认))
      • [7. `+`:更新模式 (读写)](#7. +:更新模式 (读写))
    • [2.2 文件指针 (File Pointer)](#2.2 文件指针 (File Pointer))
      • [1. `tell()`:获取当前指针位置](#1. tell():获取当前指针位置)
      • [2. `seek(offset, whence=0)`:移动指针到指定位置](#2. seek(offset, whence=0):移动指针到指定位置)
      • [3. 内存映射文件(先简单了解)](#3. 内存映射文件(先简单了解))
  • 三、文件与目录管理
    • [3.1 路径处理:`os.path` 子模块](#3.1 路径处理:os.path 子模块)
      • [1. `os.path.join()`:智能拼接路径 (跨平台)](#1. os.path.join():智能拼接路径 (跨平台))
      • [2. `os.path.abspath()`:获取绝对路径](#2. os.path.abspath():获取绝对路径)
      • [3. `os.path.basename()`:获取文件名](#3. os.path.basename():获取文件名)
      • [4. `os.path.dirname()`:获取文件所在目录](#4. os.path.dirname():获取文件所在目录)
      • [5. `os.path.exists()`:判断路径是否存在](#5. os.path.exists():判断路径是否存在)
      • [6. `os.path.isfile()` / `os.path.isdir()`:判断是文件还是目录](#6. os.path.isfile() / os.path.isdir():判断是文件还是目录)
      • [7. `os.path.getsize()`:获取文件大小](#7. os.path.getsize():获取文件大小)
    • [3.2 文件操作](#3.2 文件操作)
      • [1. `os.rename()`:重命名文件或目录](#1. os.rename():重命名文件或目录)
      • [2. `os.remove()`:删除文件](#2. os.remove():删除文件)
    • [3.3 目录操作](#3.3 目录操作)
      • [1. `os.getcwd()`:获取当前工作目录](#1. os.getcwd():获取当前工作目录)
      • [2. `os.chdir()`:改变当前工作目录](#2. os.chdir():改变当前工作目录)
      • [3. `os.listdir()`:列出目录下的所有文件和子目录](#3. os.listdir():列出目录下的所有文件和子目录)
      • [4. `os.mkdir()`:创建单层目录](#4. os.mkdir():创建单层目录)
      • [5. `os.makedirs()`:创建多层目录](#5. os.makedirs():创建多层目录)
      • [6. `os.rmdir()`:删除空目录](#6. os.rmdir():删除空目录)
      • [7. `shutil.rmtree()`:递归删除目录 (危险操作!)](#7. shutil.rmtree():递归删除目录 (危险操作!))
  • 四、现代路径操作:pathlib模块
    • [4.1 `pathlib` 简介:面向对象的路径处理方式](#4.1 pathlib 简介:面向对象的路径处理方式)
    • [4.2 创建路径对象:`Path()`](#4.2 创建路径对象:Path())
    • [4.3 路径拼接与操作:使用 `/` 运算符](#4.3 路径拼接与操作:使用 / 运算符)
    • [4.4 路径属性访问:`.name`, `.parent`, `.stem`, `.suffix`](#4.4 路径属性访问:.name, .parent, .stem, .suffix)
    • [4.5 路径状态检查:`.exists()`, `.is_file()`, `.is_dir()`](#4.5 路径状态检查:.exists(), .is_file(), .is_dir())
    • [4.6 文件操作:`.read_text()`, `.write_text()`, `.read_bytes()`, `.write_bytes()`](#4.6 文件操作:.read_text(), .write_text(), .read_bytes(), .write_bytes())
    • [4.7 目录迭代:`.iterdir()`, `.glob()`](#4.7 目录迭代:.iterdir(), .glob())
  • 五、处理常见文件格式
    • [5.1 CSV (逗号分隔值) 文件:`csv` 模块](#5.1 CSV (逗号分隔值) 文件:csv 模块)
      • [1. 读取CSV:`csv.reader`](#1. 读取CSV:csv.reader)
      • [2. 写入CSV:`csv.writer`](#2. 写入CSV:csv.writer)
      • [3. 字典形式读写:`csv.DictReader`, `csv.DictWriter`](#3. 字典形式读写:csv.DictReader, csv.DictWriter)
    • [5.2 JSON (JavaScript对象表示法) 文件:`json` 模块](#5.2 JSON (JavaScript对象表示法) 文件:json 模块)
      • [1. 将Python对象写入JSON文件:`json.dump()`](#1. 将Python对象写入JSON文件:json.dump())
      • [2. 从JSON文件读取到Python对象:`json.load()`](#2. 从JSON文件读取到Python对象:json.load())
      • [3. 字符串与Python对象的转换:`json.dumps()`, `json.loads()`](#3. 字符串与Python对象的转换:json.dumps(), json.loads())
  • 六、序列化与对象持久化
    • [6.1 `pickle` 模块:Python专用二进制协议](#6.1 pickle 模块:Python专用二进制协议)
    • [6.2 序列化 (Pickling):`pickle.dump()` - 将任意Python对象存入文件](#6.2 序列化 (Pickling):pickle.dump() - 将任意Python对象存入文件)
    • [6.3 反序列化 (Unpickling):`pickle.load()` - 从文件恢复Python对象](#6.3 反序列化 (Unpickling):pickle.load() - 从文件恢复Python对象)
    • [6.4 `pickle` 的优缺点 (速度快,但存在安全风险且不跨语言)](#6.4 pickle 的优缺点 (速度快,但存在安全风险且不跨语言))
  • 七、高级主题与最佳实践
    • [7.1 字符编码 (Character Encoding)](#7.1 字符编码 (Character Encoding))
      • [1. 编码的重要性 (如 `utf-8`, `gbk`)](#1. 编码的重要性 (如 utf-8, gbk))
      • [2. 在 `open()` 中指定 `encoding` 参数避免乱码](#2. 在 open() 中指定 encoding 参数避免乱码)
    • [7.2 异常处理 (Error Handling)](#7.2 异常处理 (Error Handling))
      • [1. `FileNotFoundError`:文件不存在](#1. FileNotFoundError:文件不存在)
      • [2. `PermissionError`:权限不足](#2. PermissionError:权限不足)
      • [3. `IsADirectoryError`: 尝试像文件一样打开目录](#3. IsADirectoryError: 尝试像文件一样打开目录)
      • [4. 使用 `try...except` 块处理文件相关异常](#4. 使用 try...except 块处理文件相关异常)
    • [7.3 文件缓冲 (File Buffering)](#7.3 文件缓冲 (File Buffering))
      • [1. 概念理解:数据并非立即写入磁盘](#1. 概念理解:数据并非立即写入磁盘)
      • [2. `file.flush()`:手动将缓冲区内容写入磁盘](#2. file.flush():手动将缓冲区内容写入磁盘)
    • [7.4 临时文件和目录:`tempfile` 模块](#7.4 临时文件和目录:tempfile 模块)
    • [7.5 内存中的文件:`io.StringIO` 和 `io.BytesIO`](#7.5 内存中的文件:io.StringIOio.BytesIO)
  • 总结

概述

Python的设计之初就是四个字"化繁为简",这四个字在文件这一章节体现的淋漓尽致,相比于Java的复杂操作简化的太多了。不过我们还是会依照Java进行比对,针对某些复杂的场景Python和Java究竟哪个更有优势。

一、文件操作基础

1.1 open() 函数:打开文件

1. 基本语法:open(file, mode='r', encoding=None)

python 复制代码
# 打开一个文件用于读取
# f 是我们得到的文件对象
f = open('my_document.txt', 'r', encoding='utf-8')

# ... 在这里进行读写操作 ...

# 操作完成后,必须关闭文件
f.close()

2. 关键参数:file (文件名/路径), mode (模式), encoding (编码)

  • file (文件名/路径):一个字符串,指定要打开的文件的路径。
    • 相对路径: 如 'my_document.txt',表示文件位于当前Python脚本运行的目录下。
    • 绝对路径: 如 'C:\Users\Admin\Documents\my_document.txt' (Windows) 或 '/home/user/documents/my_document.txt' (Linux/macOS)。
  • mode (模式):一个字符串,决定了打开文件的目的。最基础的模式有:
    • 'r':Read (读取) - 默认模式。如果文件不存在,会抛出 FileNotFoundError 异常。
    • 'w':Write (写入) - 如果文件存在,会清空原有内容;如果文件不存在,则会创建新文件。
    • 'a':Append (追加) - 在文件末尾追加内容。如果文件不存在,则会创建新文件。

(更多模式如 'b', '+' 等将在第二章详细介绍。)

  • encoding (编码):指定用于解码(读取时)或编码(写入时)文件的编码格式。

    • 这是一个至关重要的参数,尤其是在处理文本文件时。
    • 如果不指定,Python会使用操作系统的默认编码,这可能导致在不同系统上(如Windows的gbk和Linux/macOS的utf-8)出现乱码问题。

完整的open语法如下:

python 复制代码
def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True)

一般会使用到的其它参数有:

  • buffering:缓冲区大小,可以看下面的内容进行了解。
  • errors :指定在编码或解码过程中遇到无法处理的字符时该怎么办。
    • 只在文本模式下有效。
    • 'strict':默认值。如果遇到编码错误,立即抛出 UnicodeError 异常。
    • 'ignore':忽略无法解码的字符。这可能会导致数据丢失,需谨慎使用。
    • 'replace':将无法解码的字符替换为一个标记,通常是问号 ?
  • newline :控制"通用换行符模式"如何工作。
    • 只在文本模式下有效。
    • None:默认值。启用通用换行符。读取时,'\n', '\r', '\r\n' 都会被统一转换为 '\n'。写入时,'\n' 会被转换成系统的默认行分隔符(Windows上是\r\n,Linux/macOS上是\n)。
    • ' ' (空字符串):不进行任何换行符转换。读取时按原样返回,写入时\n就是\n。在处理CSV文件时, 通常需要设置为 newline='',以防止 csv 模块自己处理换行时出现空行。
    • '\n', '\r', '\r\n': 写入时,强制使用指定的字符作为换行符。

3. 与Java的对比

python 复制代码
// 需要处理可能抛出的异常
try {
    // 1. 创建一个FileOutputStream来连接到物理文件
    FileOutputStream fos = new FileOutputStream("data.txt");
    // 2. 创建一个OutputStreamWriter来指定编码
    OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
    // 3. 使用BufferedWriter来获得缓冲功能,提高效率
    BufferedWriter writer = new BufferedWriter(osw);

    // ... 在这里使用 writer 对象进行写入 ...

    writer.close(); // 关闭流
} catch (IOException e) {
    e.printStackTrace();
}

可以看出Java打开文件的步骤很繁琐,Python只需要一行即可。

1.2 读取文件内容

假设文件名为poem.txt,内容如下

txt 复制代码
Hello, world.
Welcome to Python.
Enjoy your journey.

1. read(size):读取指定字节数或全部内容

  • size参数是指定一次读取文件大小的,如果不指定就读取全部,最后返回一个字符串。
  • 但是一定要注意⚠️:如果文件太大会导致内存耗尽,因为文件读取的操作大部分都是经过内存的,内存往往没有那么大。
python 复制代码
with open('poem.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print(content)
# 输出:
# Hello, world.
# Welcome to Python.
# Enjoy your journey.

2. readline():读取一行内容

  • 每次调用 f.readline() 会读取文件中的一行(直到并包括换行符 \n)。
  • 当文件读取到末尾时,它会返回一个空字符串 ' '。
python 复制代码
with open('poem.txt', 'r', encoding='utf-8') as f:
    line1 = f.readline()
    print(f"第一行: {line1.strip()}") # .strip() 去掉末尾的换行符
    line2 = f.readline()
    print(f"第二行: {line2.strip()}")
# 输出:
# 第一行: Hello, world.
# 第二行: Welcome to Python.

注意这里的with关键字,这是Python中替代try except finally的重要属性,会自动关闭资源,即使出现了异常

3. readlines():读取所有行并返回一个列表

  • f.readlines() 会一次性读取所有行,并将它们作为一个字符串列表返回。列表中的每个元素都是文件的一行(包含末尾的换行符)
  • ⚠️警告: 与 read() 类似,这也可能消耗大量内存。
python 复制代码
with open('poem.txt', 'r', encoding='utf-8') as f:
    lines = f.readlines()
    print(lines)
# 输出:
# ['Hello, world.\n', 'Welcome to Python.\n', 'Enjoy your journey.']

4. 迭代文件对象:for line in file: (内存效率最高的方式)

  • 一次读取一行到内存,效率最高。
python 复制代码
with open('poem.txt', 'r', encoding='utf-8') as f:
    for line in f:
        print(line.strip()) # 推荐在循环内部处理每一行
# 输出:
# Hello, world.
# Welcome to Python.
# Enjoy your journey.
  • Java8之后的stream流简化了操作,但Python依旧是经典
java 复制代码
try (Stream<String> stream = Files.lines(Paths.get("poem.txt"))) {
    stream.forEach(System.out::println);
}

1.3 写入文件内容

写入操作需要以 'w' 或 'a' 模式打开文件。

1. write(string):将字符串写入文件

  • 此方法将指定的字符串写入文件。
  • 注意: write() 不会自动添加换行符。你需要手动添加 \n。
python 复制代码
with open('output.txt', 'w', encoding='utf-8') as f:
    f.write("这是第一行。\n")
    f.write("这是第二行。")

2. writelines(list_of_strings):将一个字符串列表写入文件

  • 此方法接收一个字符串列表(或任何可迭代对象),并将其中每个字符串连续写入文件。
  • 注意: 和 write() 一样,writelines() 也不会在元素之间添加任何分隔符或换行符
python 复制代码
lines_to_write = ['第一行\n', '第二行\n', '第三行\n']
with open('output_lines.txt', 'w', encoding='utf-8') as f:
    f.writelines(lines_to_write)

注意在Java中有一个newLine方法可以避免我们手动插入换行符,但目前来看,大多数系统已经完全支持\n操作符。

java 复制代码
// BufferedWriter 提供了更高效的写入
writer.write("some string");
writer.newLine(); // 推荐使用 newLine() 来换行,它会写入平台相关的换行符

1.4 关闭文件

1. close() 方法:释放文件资源

python 复制代码
f = open('data.txt', 'w')
try:
    f.write('hello')
finally:
    # 无论 try 块中是否发生错误,finally 块都会执行
    f.close()

2. 文件自动关闭:with open(...) as f: 语句块 (推荐)

无论 with 块内部的代码是正常执行完毕,还是中途发生异常,Python都会自动保证文件被正确关闭。

python 复制代码
# 这段代码等价于上面的 try...finally 结构,但更简洁、更安全
with open('data.txt', 'w', encoding='utf-8') as f:
    f.write('Hello, Python!')
# 当代码执行离开 with 语句块时,f.close() 会被自动调用

3. 为什么必须关闭文件:数据缓冲刷新、系统资源限制

  1. 系统资源限制:操作系统能同时打开的文件句柄数量是有限的。如果程序打开了大量文件却不关闭,会耗尽系统资源,导致程序或系统崩溃。
  2. 数据缓冲:为了提高效率,向文件写入数据时,内容通常会先存放在内存的缓冲区中,当缓冲区满了或文件关闭时,才会一次性写入磁盘。如果不关闭文件,你写入的最后一部分数据可能永远留在缓冲区里,导致数据丢失。

思考:为什么类似抖音这种平台读写速度很快?怎么能从程序和硬件上优化系统的读写速度?

按照上面所说,一个系统可以打开的文件句柄是有上限的,例如Linux系统,一个进程默认只能打开1024个文件句柄,整个系统的上限大概在几十万------几百万之间,而抖音这种平台可能同时在线的用户都在几千万甚至上亿,到底是怎么处理的呢?

抖音平台怎么处理
维度/关注点 无状态的HTTP流式传输 HTTPS传输
核心概念 一种数据传输策略 一个安全通信协议
解决的问题 如何高效、可扩展地传输大文件,减少等待时间和资源消耗。 如何保证通信过程的机密性、完整性和对方身份的真实性。
关键技术 HTTP Range请求、分块传输编码 (Chunked Transfer Encoding) SSL/TLS 加密、数字证书、哈希算法
生活中的比喻 分期送货。一个大包裹(视频)分成很多小件,每次只送一件。快递员(服务器)送完就走,不记得到底送了多少件。 使用武装押运车送货。无论送的是什么,送几次,整个运输过程都被严密保护,防止被偷窥和抢劫。
它们的关系 正交、可组合。它们是两个不同维度的事情。 正交、可组合。HTTPS 为 HTTP 提供了安全层。
  1. 无状态的http流式传输

上面的表格有两个很重要的特性:无状态流传输。而https是安全的、可控、可追溯的一种协议,不会使用在视频或图片等。

  • 无状态每一次HTTP请求都是完全独立的、自包含的。服务器不会记录客户端之前的任何请求信息,类似看到哪里了等等,而且即使某台服务器宕机,立马会转发请求到另一台服务器,无缝衔接。
  • 流传输服务器一块一块的发送,客户端一块一块的接收,块的大小可以调整,保证了及时的响应,也不会拖慢速度,因为内存中根本就不会存在整个文件。
  1. 内容分发网络(CDN)

这是非常重要的核心,视频这种文件都是存放在专业的分布式系统中,通过CDN的方式进行全球分发。

  • 源站 (Origin Server):视频上传后,被存放在一个高可靠的中心存储,比如Amazon S3, Google Cloud Storage等。
  • 边缘节点 (Edge Nodes):CDN在全球各地部署了成千上万的缓存服务器,这些就是边缘节点。
  • 智能DNS解析 :当您在北京刷抖音时,DNS会把视频的请求解析到离您最近的速度最快的北京或天津的CDN边缘节点上
  • 缓存命中 (Cache Hit):如果这个视频已经被其他北京用户看过,它很可能已经被缓存(Cache)在该节点上了。节点会直接从自己的高速硬盘/内存中把数据块发给您,根本不需要请求源站

CDN的作用是决定性的

  • 极低延迟:用户从地理位置最近的服务器获取数据。
  • 极高并发:上亿用户的请求被分散到了全球成千上万个CDN节点上,每个节点只需要处理一小部分流量。
  • 保护源站:源站的负载被大大降低,可能一个热门视频一天也只被源站读取几次(被不同地区的CDN节点首次请求时)。

何为分布式系统,注意哈,这是系统,不是服务器,就一句话:用一堆普通的、廉价的服务器,通过精巧的软件设计和协议,组合成一个超级稳定、海量容量、性能卓越的"虚拟"存储巨无霸
分布式系统的核心是将数据分块然后分布存储,即使有多台服务器宕机,你的数据也不会丢失,就像一个视频会切割为几百几千份,然后复制多份存放在不同地区的服务器。

系统名称 类型 典型应用场景 简单说明
HDFS 文件系统 大数据分析(Hadoop生态) 适合一次写入、多次读取的批量处理场景,吞吐量高。
Ceph 统一存储 云平台(OpenStack, Kubernetes) 能同时提供对象存储、块存储、文件系统三种服务,非常灵活。
Amazon S3 对象存储 互联网应用、静态网站、备份归档 通过简单的API(PUT/GET)来存储海量非结构化数据(图片、视频)。
GlusterFS 文件系统 媒体仓库、日志存储 通过堆叠普通服务器形成一个大容量的分布式文件系统。
Redis Cluster 内存数据库 高速缓存、会话存储 将数据分片存储在多个Redis节点的内存中,性能极高。
Cassandra 数据库 写入密集型的Web应用(如消息、物联网) 无中心架构,所有节点平等,扩展性和可用性极好。
  1. 负载均衡

即使在同一个CDN区域,也不会只有一台服务器。请求会先到达一个负载均衡器(熟悉吧,nginx是我们常用的),它会像交通警察一样,把请求分发给后面一组服务器中当前最空闲的一台。这确保了没有单台服务器会被压垮。

从程序和硬件上优化系统的读写速度

程序与软件层面

  1. 使用缓冲 (Buffering) :这是最基础也是最重要的优化。直接对文件进行频繁的小数据块读写,会导致大量的系统调用,开销巨大。缓冲I/O会将多次小写入合并成一次大写入,或者预先读取一大块数据到内存中,从而显著减少系统调用次数。
  • Python实践: open()函数默认就是带缓冲的。你可以通过buffering参数控制缓冲区大小。
  • Java实践: FileInputStream是无缓冲的,而BufferedInputStream和BufferedWriter则提供了缓冲功能,通常推荐组合使用它们。
  1. 异步I/O (Asynchronous I/O):在传统(同步)I/O中,当程序发起一个读写请求时,它会被阻塞,直到操作完成。在异步I/O中,程序发起请求后可以立即返回,继续做其他事情。当I/O操作完成时,操作系统会通过回调、事件等方式通知程序。

  2. 内存映射文件 (Memory-Mapped Files) :这是一种高级技术,它将文件的一部分或全部内容直接映射到进程的虚拟地址空间。之后,你可以像操作内存数组一样直接读写文件内容,省去了read()/write()的系统调用和内存拷贝。操作系统会负责在需要时将内存中的修改同步回磁盘。

  3. 选择合适的数据结构和序列化格式

  • 文本 vs. 二进制: JSON, XML, CSV等文本格式可读性好,但解析慢、体积大。像Protocol Buffers, Avro, Parquet等二进制格式通常更紧凑,读写速度快得多。

  • 数据压缩: 对数据进行压缩(如Gzip, Snappy)可以减少磁盘I/O量,但会增加CPU开销。需要根据CPU和磁盘速度进行权衡。

  1. 利用操作系统的特性
  • 例如Linux的sendfile()系统调用,可以直接在两个文件描述符之间(如文件和网络socket)传输数据,完全在内核态完成,避免了数据在内核和用户空间之间的多次拷贝,效率极高。很多高性能Web服务器(如Nginx)都用它来发送静态文件。

硬件与系统配置层面 (Hardware & System Configuration)

  1. 存储介质升级(效果最显著):
  • HDD (机械硬盘): 速度最慢,依赖物理磁头的寻道和旋转,随机读写性能很差。
  • SATA SSD (固态硬盘): 比HDD快一个数量级,没有机械部件,随机读写性能优秀。
  • NVMe SSD: 目前消费级和企业级的顶级选择,通过PCIe总线直接与CPU连接,延迟更低,速度比SATA SSD快几倍到几十倍。
  1. 增加内存 (RAM):操作系统会利用所有空闲的内存作为页面缓存 (Page Cache)。当你读取一个文件时,操作系统会将其内容缓存到RAM中。下次再读取同一文件时,如果它还在缓存里,就会直接从飞快的RAM中获取,而不是慢速的磁盘。更多的RAM意味着更大的缓存,能命中缓存的概率就更高。

  2. 使用RAID (磁盘阵列):通过组合多块硬盘来提升性能或可靠性。

  • RAID 0 (条带化): 将数据分块写入多块硬盘,读写速度理论上是单块硬盘的N倍(N为硬盘数量)。缺点是没有数据冗余,一块硬盘损坏,所有数据丢失。
  • RAID 10 (镜像+条带): 兼具RAID 0的速度和RAID 1的可靠性,是数据库等高性能、高可用场景的常用选择。
  1. 选择合适的文件系统 (File System):不同的文件系统有不同的特性和性能表现。
  • ext4: Linux下最常见、最成熟的选择,综合性能好。
  • XFS: 对于处理海量大文件有性能优势。
  • ZFS/Btrfs: 提供快照、内建压缩、数据校验等高级功能,但可能带来一些性能开销。
  1. CPU: 虽然I/O操作本身不主要消耗CPU,但相关任务如数据压缩/解压、序列化/反序列化、加密/解密等都是CPU密集型的。一颗强大的CPU可以更快地处理数据,为I/O操作做好准备。

二、文件模式与指针控制

2.1 文件访问模式

模式 描述 文件不存在 指针位置 读写权限
r 只读 报错 开头 只能读
r+ 读写 报错 开头 可读可写
w 只写 创建 开头 只能写(会清空文件)
w+ 读写 创建 开头 可读可写(会清空文件)
a 追加只写 创建 结尾 只能写(追加)
a+ 追加读写 创建 结尾 可读可写(追加)
x 排他创建 必须不存在 开头 只能写

1. r:只读 (默认)

  • open()函数的默认模式。如果文件不存在,会抛出FileNotFoundError 异常。尝试写入文件会抛出UnsupportedOperation异常。
python 复制代码
try:
    f = open('data.txt', 'r')
    content = f.read()
    print(content)
    f.close()
except FileNotFoundError:
    print("文件不存在")

2. w:写入 (文件存在则清空,不存在则创建)

  • 如果文件存在,会立即清空文件内容;如果文件不存在,则会创建一个新文件。此模式只能写入,不能读取。
python 复制代码
# 这会覆盖data.txt的全部内容
with open('data.txt', 'w') as f:
    f.write('Hello, World!')

3. a:追加 (在文件末尾写入,不存在则创建)

  • 如果文件存在,新的内容会追加到文件末尾;如果文件不存在,则会创建一个新文件。此模式只能写入,不能读取
python 复制代码
with open('log.txt', 'a') as f:
    f.write('New log entry.\n')

4. x:独占创建 (文件已存在则报错)

  • 只用于创建新文件并写入。如果文件已经存在,会抛出FileExistsError异常。这是一种更安全的文件创建方式,可以避免意外覆盖。
python 复制代码
try:
    with open('config.json', 'x') as f:
        f.write('{}')
except FileExistsError:
    print("配置文件已存在,无法创建。")

接下来的三种是模式修饰符,和上述的r、w、a、x 组合使用

5. b:二进制模式 (用于图片、视频等非文本文件)

  • 用于处理非文本文件,如图片、音频、视频等。在二进制模式下,读写的是字节(bytes)而不是字符串(str)。它不进行任何编码或换行符转换
python 复制代码
with open('image.jpg', 'rb') as f:
    image_data = f.read() # image_data是bytes类型

with open('image_copy.jpg', 'wb') as f:
    f.write(image_data)

6. t:文本模式 (默认)

  • 这是默认模式,用于处理文本文件。它会自动处理平台相关的行尾符(如Windows的\r\n转为\n),并使用默认或指定的编码(如UTF-8)对内容进行编解码。
  • Java中所有基于Reader和Writer的类(如FileReader, FileWriter)都是处理文本数据的。它们在内部处理字符编码。

7. +:更新模式 (读写)

  • 基础模式扩展为读写。例如,r+是可读可写的只读模式(文件必须存在),w+是可读可写的写入模式(文件被清空),a+是可读可写的追加模式(初始指针在末尾)。

2.2 文件指针 (File Pointer)

💡 文件指针可以想象成文本编辑器中的光标。它标记了下一次读写操作将要发生的位置,以字节为单位

1. tell():获取当前指针位置

  • 返回一个整数,表示指针当前位置距离文件开头的字节数
python 复制代码
with open('data.txt', 'rb') as f:
    f.read(5)  # 读取5个字节
    position = f.tell()
    print(f"读取5字节后,指针位置在: {position}") # 输出: 读取5字节后,指针位置在: 5

2. seek(offset, whence=0):移动指针到指定位置

  • offset:要移动的字节数。
  • whence :参考点。
    • whence=0:从文件开头计算 (默认)
    • whence=1:从当前位置计算
    • whence=2:从文件末尾计算
  • 注意:在文本模式下该方法受限,尽量在二进制模式下使用,常见的使用就在视频中。
python 复制代码
with open('data.bin', 'rb') as f:
    # whence=0: 移动到第10个字节处
    f.seek(10)
    print(f"当前位置: {f.tell()}") # 10

    # whence=1: 从当前位置向后移动5个字节
    f.seek(5, 1)
    print(f"当前位置: {f.tell()}") # 15

    # whence=2: 移动到文件末尾倒数8个字节处
    f.seek(-8, 2)
    print(f"当前位置: {f.tell()}")

3. 内存映射文件(先简单了解)

对于超大文件,反复使用read和seek可能会导致性能瓶颈。Python的mmap模块和Java的java.nio.MappedByteBuffer允许你将文件的一部分直接映射到内存中。操作系统会负责处理实际的I/O,你可以像操作一个巨大数组一样操作文件,性能极高。

三、文件与目录管理

3.1 路径处理:os.path 子模块

💡 os.path模块的核心思想是:对路径字符串进行操作,而不关心路径是否真实存在

1. os.path.join():智能拼接路径 (跨平台)

  • 这是处理路径拼接的正确方式。它会根据当前操作系统自动使用正确的路径分隔符(Windows上是\,Linux/macOS上是/)。
python 复制代码
import os
path = os.path.join('home', 'user', 'documents', 'file.txt')
print(path) # 在Linux上输出 'home/user/documents/file.txt'
            # 在Windows上输出 'home\\user\\documents\\file.txt'

2. os.path.abspath():获取绝对路径

  • 将一个相对路径(如'./data.csv')转换为从根目录开始的完整路径。
python 复制代码
import os
abs_path = os.path.abspath('myfile.txt')
print(abs_path) # 输出如 '/home/user/project/myfile.txt'

3. os.path.basename():获取文件名

  • 从一个完整路径中提取出最后一部分,通常是文件名或目录名。
python 复制代码
import os
path = '/home/user/data.csv'
filename = os.path.basename(path)
print(filename) # 输出 'data.csv'

再一次感慨,为什么Python方便了,这在Java一般都需要自己写一个工具类实现。

4. os.path.dirname():获取文件所在目录

  • 从一个完整路径中提取出除了最后一部分之外的所有内容。
python 复制代码
import os
path = '/home/user/data.csv'
dir_name = os.path.dirname(path)
print(dir_name) # 输出 '/home/user'

5. os.path.exists():判断路径是否存在

  • 查一个路径(文件或目录)是否真实存在于文件系统中。返回True或False。
python 复制代码
import os
if os.path.exists('/home/user/data.csv'):
    print("文件存在")

6. os.path.isfile() / os.path.isdir():判断是文件还是目录

  • isfile()判断路径是否存在且为文件,isdir()判断是否存在且为目录。
python 复制代码
import os
path = '/home/user'
print(f"是文件吗? {os.path.isfile(path)}") # False
print(f"是目录吗? {os.path.isdir(path)}")  # True

7. os.path.getsize():获取文件大小

  • 返回文件的大小,单位是字节。如果路径是目录或不存在,会抛出异常。
python 复制代码
import os
size_in_bytes = os.path.getsize('my_large_file.zip')
print(f"文件大小: {size_in_bytes / 1024:.2f} KB")

3.2 文件操作

1. os.rename():重命名文件或目录

  • 将src(源路径)重命名为dst(目标路径)。也可以用来移动文件。
python 复制代码
import os
# 创建一个源文件
with open('old_name.txt', 'w') as f:
    f.write('rename test')

print(f"重命名前,'old_name.txt' 是否存在: {os.path.exists('old_name.txt')}")

# 执行重命名
os.rename('old_name.txt', 'new_name.txt')

print(f"重命名后,'old_name.txt' 是否存在: {os.path.exists('old_name.txt')}")
print(f"重命名后,'new_name.txt' 是否存在: {os.path.exists('new_name.txt')}")
os.remove('new_name.txt') # 清理

虽然给出的示例是直接的文件名,但因为这是基于当前项目的路径,一般都是需要完整的路径进行重命名和移动。

2. os.remove():删除文件

  • 删除指定路径的文件。如果路径是一个目录,会抛出IsADirectoryError。
python 复制代码
import os
# 创建一个待删除的文件
with open('file_to_delete.txt', 'w') as f:
    f.write('delete me')

print(f"删除前,文件是否存在: {os.path.exists('file_to_delete.txt')}")

# 执行删除
os.remove('file_to_delete.txt')

print(f"删除后,文件是否存在: {os.path.exists('file_to_delete.txt')}")

3.3 目录操作

1. os.getcwd():获取当前工作目录

  • 返回程序当前正在运行的目录路径。
python 复制代码
import os
current_dir = os.getcwd()
print(f"当前工作目录是: {current_dir}")

Java一般是:System.getProperty("user.dir")

2. os.chdir():改变当前工作目录

  • 将程序的当前工作目录更改为指定的路径。
python 复制代码
import os
original_dir = os.getcwd()
print(f"原始目录: {original_dir}")

# 创建并进入一个新目录
if not os.path.exists('temp_dir'): os.mkdir('temp_dir')
os.chdir('temp_dir')
print(f"切换后目录: {os.getcwd()}")

# 切回原始目录
os.chdir(original_dir)
print(f"切回后目录: {os.getcwd()}")
os.rmdir('temp_dir') # 清理

⚠️ 注意:这个方法一定要慎用,这是基于进程维度的操作,如果你变了,那整个系统都会受到影响(仅限使用相对路径读取文件的时候),同时对一些方法的使用也会有影响,比如同时存在两个DateUtils,本来A的优先级高,结果切换后导致B的优先级高。

3. os.listdir():列出目录下的所有文件和子目录

  • 返回一个列表,包含指定目录下所有文件和子目录的名称。
python 复制代码
import os
# 创建一个测试环境
os.mkdir('list_dir')
open(os.path.join('list_dir', 'file.txt'), 'w').close()
os.mkdir(os.path.join('list_dir', 'subdir'))

# 列出内容
contents = os.listdir('list_dir')
print(f"'list_dir' 目录下的内容: {contents}") # 输出可能为 ['file.txt', 'subdir'] (顺序不定)

# 清理
os.remove(os.path.join('list_dir', 'file.txt'))
os.rmdir(os.path.join('list_dir', 'subdir'))
os.rmdir('list_dir')

注意:这可不自动递归子目录,就是直接下级。

4. os.mkdir():创建单层目录

  • 创建一个目录。如果父目录不存在,会抛出FileNotFoundError。
python 复制代码
import os
dir_name = 'single_dir'
if not os.path.exists(dir_name):
    os.mkdir(dir_name)
    print(f"目录 '{dir_name}' 创建成功。")
    print(f"目录是否存在: {os.path.isdir(dir_name)}")
    os.rmdir(dir_name) # 清理

5. os.makedirs():创建多层目录

  • 递归创建目录。如果中间目录不存在,会自动创建。
python 复制代码
import os
import shutil
path = os.path.join('data', 'images', 'thumbnails')
if not os.path.exists(path):
    os.makedirs(path)
    print(f"多层目录 '{path}' 创建成功。")
    print(f"路径是否存在: {os.path.exists(path)}")
    shutil.rmtree('data') # 使用shutil清理整个树

6. os.rmdir():删除空目录

  • 删除一个空目录。如果目录不为空,会抛出OSError。
python 复制代码
import os
dir_name = 'empty_folder'
os.mkdir(dir_name) # 创建一个空目录
print(f"删除前,目录是否存在: {os.path.exists(dir_name)}")

os.rmdir(dir_name) # 删除它
print(f"删除后,目录是否存在: {os.path.exists(dir_name)}")

7. shutil.rmtree():递归删除目录 (危险操作!)

  • 来自shutil模块。它会删除一个目录及其包含的所有内容,无论目录是否为空。使用时必须极其小心。
python 复制代码
import os
import shutil

# 创建一个复杂的目录结构
os.makedirs('tree_to_delete/subfolder')
with open('tree_to_delete/subfolder/file.txt', 'w') as f:
    f.write('test')

path = 'tree_to_delete'
print(f"删除前,目录树是否存在: {os.path.exists(path)}")

# !! 危险操作 !!
shutil.rmtree(path)

print(f"删除后,目录树是否存在: {os.path.exists(path)}")

总之,这个方法慎用。

四、现代路径操作:pathlib模块

4.1 pathlib 简介:面向对象的路径处理方式

💡 在pathlib出现之前,处理路径需要导入os.path模块并使用一系列函数,如os.path.join(), os.path.exists()。这些函数接收和返回字符串。pathlib改变了这一点,它让你创建一个Path对象,然后直接在这个对象上调用方法,如path.exists()。这种方式将数据(路径本身)和操作(检查存在性、获取父目录等)封装在一起,是典型的面向对象设计

怎么说呢,Java中文件的类型是File,类自带一些方法,包括后面出现的NIO中的Path,Files工具类等都是面向对象编程,而Python中大多数都是基于os模块将文件传入进行操作的,所以也出现了Path对象,只不过我感觉不太习惯,因为毕竟是Path,老是把它当做一个路径。

4.2 创建路径对象:Path()

  • 从pathlib模块导入Path类,然后用一个字符串路径来实例化它。你也可以使用类方法Path.cwd()获取当前工作目录或Path.home()获取用户主目录。
python 复制代码
from pathlib import Path

# 从字符串创建Path对象
p = Path('documents/report.docx')
print(p)

# 获取当前工作目录
current_dir = Path.cwd()
print(f"当前目录: {current_dir}")

# 获取用户主目录
home_dir = Path.home()
print(f"主目录: {home_dir}")

4.3 路径拼接与操作:使用 / 运算符

  • 这是pathlib最优雅和最受欢迎的特性之一。你可以使用斜杠/运算符来拼接路径,pathlib会自动处理好不同操作系统的分隔符。这比os.path.join()更具可读性。
python 复制代码
from pathlib import Path

home = Path.home()
# 使用 / 运算符进行路径拼接
full_path = home / 'documents' / 'projects' / 'main.py'
print(full_path) 
# 在Linux上输出: /home/user/documents/projects/main.py
# 在Windows上输出: C:\Users\user\documents\projects\main.py

4.4 路径属性访问:.name, .parent, .stem, .suffix

💡 Path对象提供了方便的属性来直接获取路径的各个部分,无需进行字符串解析

  • .name: 路径的最后一部分(文件名或目录名)。
  • .parent: 包含此路径的父目录。
  • .stem: 文件名中去掉最后一个后缀的部分。
  • .suffix: 最后一个后缀名(包括点)。
python 复制代码
from pathlib import Path
p = Path('/home/user/data/archive.tar.gz')

print(f"完整名称: {p.name}")      # 'archive.tar.gz'
print(f"父目录:   {p.parent}")    # '/home/user/data'
print(f"文件名主干: {p.stem}")      # 'archive.tar' (注意:只去掉最后一个后缀)
print(f"后缀:     {p.suffix}")    # '.gz'
print(f"所有后缀:   {p.suffixes}")  # ['.tar', '.gz']

4.5 路径状态检查:.exists(), .is_file(), .is_dir()

  • 这些方法直接在Path对象上调用,用于查询文件系统的真实状态。
python 复制代码
from pathlib import Path
p = Path('.') # 当前目录
print(f"'{p}' 是否存在? {p.exists()}") # True
print(f"'{p}' 是文件吗? {p.is_file()}")   # False
print(f"'{p}' 是目录吗? {p.is_dir()}")    # True

4.6 文件操作:.read_text(), .write_text(), .read_bytes(), .write_bytes()

  • 这些是极其方便的快捷方法,用于简单的文件读写。它们在内部处理了打开和关闭文件的整个流程。
python 复制代码
from pathlib import Path
p = Path('greeting.txt')

# 写入文本 (自动处理 open/write/close)
p.write_text('Hello, pathlib!', encoding='utf-8')

# 读取文本
content = p.read_text(encoding='utf-8')
print(content) # 'Hello, pathlib!'

p.unlink() # 删除文件,相当于 os.remove()

Java中也有类似的方法

  • p.write_text(content) 对应 Files.writeString(p, content) (Java 11+)。
  • p.read_text() 对应 Files.readString§ (Java 11+) 或 new String(Files.readAllBytes§)。
  • p.write_bytes(data) 对应 Files.write(p, data)。
  • p.read_bytes() 对应 Files.readAllBytes§。

4.7 目录迭代:.iterdir(), .glob()

  • .iterdir(): 返回一个迭代器,用于遍历目录下的直接子项(文件和子目录)。
  • .glob(pattern): 更强大,支持通配符模式匹配。
    • *.txt: 匹配所有.txt文件。
    • **/*.py: 递归匹配所有子目录下的.py文件。

这里还是要注意使用递归匹配的方法,一般在正式环境中目录的层级较深,而且文件较多,这种没有任何阻断和条件判断的递归始终存在隐患。

五、处理常见文件格式

💡 在Python中有很多模块用来处理常见的文件格式,方便我们进行处理,相比之下Java也是一样的。

5.1 CSV (逗号分隔值) 文件:csv 模块

首先你要了解什么是csv文件,CSV是一种简单的文本格式,用于存储表格数据。csv模块可以让你不必手动处理逗号、引号和换行符带来的麻烦。

csv 复制代码
Name,Department,Salary
John Doe,Engineering,90000
Jane Smith,Marketing,110000
Peter Jones,Engineering,95000

1. 读取CSV:csv.reader

  • csv.reader创建一个阅读器对象,它是一个迭代器。遍历这个迭代器,每一行都会被解析成一个字符串列表。
python 复制代码
import csv

# 关键点: 打开文件时使用 newline='' 来避免空行问题
with open('data.csv', 'r', newline='', encoding='utf-8') as f:
    reader = csv.reader(f)
    header = next(reader) # 读取并跳过头部
    print(f"头部: {header}")
    for row in reader:
        # row 是一个列表, e.g., ['John Doe', 'Engineering', '90000']
        print(f"{row[0]} in {row[1]} earns ${row[2]}")

为什么说 newline=' ' 可以避免空行,这是因为不同系统对于换行符的处理是不一样的,window是\r\n,其余大多数都是\n,如果让Python在读取文件时自动处理,那可能就导致csv的内容识别错误了,这时候使用 newline=' ' 就是把换行符交给csv模块来处理,它们的逻辑可以自动识别。

  • Java中读取csv:使用Apache Commons CSV库。
java 复制代码
// Java Example with Apache Commons CSV
Reader in = new FileReader("data.csv");
Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(in);
for (CSVRecord record : records) {
    String name = record.get("Name");
    String salary = record.get("Salary");
    System.out.println(name + " earns $" + salary);
}

2. 写入CSV:csv.writer

  • csv.writer创建一个写入器对象。使用.writerow()写入单行(接收一个列表),或.writerows()写入多行(接收一个列表的列表)。
python 复制代码
import csv

data_to_write = [
    ['Name', 'Department', 'Salary'],
    ['Alice Brown', 'HR', 75000],
    ['Bob Johnson', 'Sales', 120000]
]

with open('output.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    writer.writerows(data_to_write)
    # 或者逐行写入:
    # writer.writerow(['Charlie Davis', 'Support', 60000])
    
print("'output.csv' has been created.")
  • Java中的写入
java 复制代码
// Java Example with Apache Commons CSV
Writer out = new FileWriter("output.csv");
try (CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
    printer.printRecord("Name", "Department", "Salary");
    printer.printRecord("Alice Brown", "HR", 75000);
}

3. 字典形式读写:csv.DictReader, csv.DictWriter

  • 这是处理带标题行的CSV文件的更推荐、更健壮的方式。DictReader将每一行读取为一个字典,键是列标题。DictWriter则从字典写入行。表格的行说白了就是一个对象,只是展开了而已

读取

python 复制代码
import csv

with open('data.csv', 'r', newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        # row 是一个字典, e.g., {'Name': 'John Doe', 'Department': 'Engineering', 'Salary': '90000'}
        print(f"{row['Name']} earns ${row['Salary']}")

写入

python 复制代码
import csv

data_to_write = [
    {'Name': 'Alice Brown', 'Department': 'HR', 'Salary': 75000},
    {'Name': 'Bob Johnson', 'Department': 'Sales', 'Salary': 120000}
]
# 必须指定列的顺序
fieldnames = ['Name', 'Department', 'Salary']

with open('output_dict.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader() # 写入标题行
    writer.writerows(data_to_write)

print("'output_dict.csv' has been created.")

5.2 JSON (JavaScript对象表示法) 文件:json 模块

💡 json模块的核心是dump/load(处理文件)和dumps/loads(处理字符串)这四对函数。

💡 为什么要说json,一般和第三方对接数据首选肯定是json,本系统的不同模块对接也可以用json。

1. 将Python对象写入JSON文件:json.dump()

  • 将Python对象(主要是字典和列表)序列化为JSON格式并写入到一个文件流中。
  • indent=4:保持字符缩进为4,看起来更加美观。
  • ensure_ascii=False:允许非ASCII字符(如中文、泰文等)直接显示
python 复制代码
import json

user_data = {
    "name": "Guido van Rossum",
    "id": 1001,
    "is_active": True,
    "roles": ["BDFL", "Python Creator"],
    "settings": None
}

with open('user.json', 'w', encoding='utf-8') as f:
    # indent=4 ทำให้ไฟล์ JSON มีการจัดรูปแบบที่สวยงาม อ่านง่าย
    json.dump(user_data, f, indent=4, ensure_ascii=False)

print("'user.json' has been created.")

2. 从JSON文件读取到Python对象:json.load()

  • 从一个文件流中读取JSON数据,并将其反序列化为Python对象。JSON对象变为Python字典,数组变为列表,字符串、数字、布尔值和null也都有对应的Python类型。
python 复制代码
import json

with open('user.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

print(type(data))       # <class 'dict'>
print(data['name'])     # Guido van Rossum
print(data['roles'])    # ['BDFL', 'Python Creator']

3. 字符串与Python对象的转换:json.dumps(), json.loads()

  • dumps代表 "dump string" ,将Python对象转换成JSON格式的字符串。loads代表 "load string",将JSON格式的字符串解析成Python对象。这在处理网络API响应时非常常用。
python 复制代码
import json

# 1. Python object to JSON string (dumps)
py_object = {'city': 'Amsterdam', 'country': 'Netherlands'}
json_string = json.dumps(py_object, indent=2)
print("--- JSON String ---")
print(json_string)

# 2. JSON string to Python object (loads)
api_response_string = '{"product_id": 90210, "in_stock": true}'
py_dict = json.loads(api_response_string)
print("\n--- Python Dictionary ---")
print(py_dict)
print(f"Product ID: {py_dict['product_id']}")

六、序列化与对象持久化

💡 就是Java的Serializable接口,效果一样的,这是一个必备的实现,我们一直在使用,但大多数场景都是用来网络传输,只有在某些特定的时候需要将对象持久化到用户的本地设备。但是,没有安全保障,所以一般肯定不会使用原生的,都会使用专业的第三方。

6.1 pickle 模块:Python专用二进制协议

与JSON或CSV不同,pickle不是一种人类可读的文本格式。它是一种二进制协议,专门设计用来表示Python对象。它的强大之处在于,它几乎可以序列化任何Python对象,包括自定义类的实例、函数,甚至是递归数据结构,而这些是JSON无法处理的。它存储的是对象的"快照",保留了对象的类型和内部状态。

6.2 序列化 (Pickling):pickle.dump() - 将任意Python对象存入文件

  • pickle.dump(obj, file)函数接收两个参数:要序列化的对象obj,以及一个以二进制写入模式 ('wb') 打开的文件对象file。因为pickle生成的是字节,所以必须使用二进制模式。
python 复制代码
import pickle

# 定义一个自定义类
class Player:
    def __init__(self, name, level, inventory):
        self.name = name
        self.level = level
        self.inventory = inventory
    
    def display(self):
        print(f"Player: {self.name}, Level: {self.level}, Inventory: {self.inventory}")

# 创建一个对象实例
player1 = Player('Gandalf', 99, ['Staff of Power', 'Glamdring', 'Lembas Bread'])

# 将对象序列化到文件
# 注意:必须使用 'wb' (write binary) 模式
with open('player.pkl', 'wb') as f:
    pickle.dump(player1, f)

print("对象已成功序列化到 'player.pkl'")

6.3 反序列化 (Unpickling):pickle.load() - 从文件恢复Python对象

  • pickle.load(file)从一个以二进制读取模式 ('rb') 打开的文件对象file中读取字节流,并将其重建为一个完整的Python对象。
python 复制代码
import pickle

# 确保 Player 类的定义在当前作用域中是可用的
class Player:
    def __init__(self, name, level, inventory):
        self.name = name
        self.level = level
        self.inventory = inventory
    
    def display(self):
        print(f"Player: {self.name}, Level: {self.level}, Inventory: {self.inventory}")

# 从文件反序列化对象
# 注意:必须使用 'rb' (read binary) 模式
with open('player.pkl', 'rb') as f:
    restored_player = pickle.load(f)

print("对象已从 'player.pkl' 恢复")
print(f"恢复对象的类型: {type(restored_player)}")

# 验证对象是否完整恢复,包括它的方法
restored_player.display() 
# 输出: Player: Gandalf, Level: 99, Inventory: ['Staff of Power', 'Glamdring', 'Lembas Bread']

6.4 pickle 的优缺点 (速度快,但存在安全风险且不跨语言)

优点

  • 功能强大:可以处理绝大多数Python对象,包括复杂的自定义对象、函数和类,保留其完整的内部状态。
  • 简单易用:API非常简洁,只有dump()和load()两个核心函数。
  • 性能相对较高:作为一种紧凑的二进制格式,其序列化和反序列化的速度通常比文本格式的JSON快。

缺点

  • 安全风险 (最重要):绝对不要从不可信或未经验证的来源反序列化数据。pickle的字节流可以被恶意构造,使其在反序列化时执行任意代码,这可能导致远程代码执行漏洞。这与Java Serializable的风险完全相同。
  • 不跨语言:pickle是Python独有的格式。一个由Python程序创建的.pkl文件对于Java、C++、JavaScript等其他语言来说是无法解析的。
  • 版本兼容性问题:用较新版本的Python创建的pickle文件可能无法被较旧版本的Python正确读取。虽然pickle协议有多个版本,但这仍然是一个潜在的维护问题。

七、高级主题与最佳实践

这一部分我就不做过多解释了,都是基本的编码要求,重点说一下tempfile内存中的文件两个知识点。

7.1 字符编码 (Character Encoding)

1. 编码的重要性 (如 utf-8, gbk)

2. 在 open() 中指定 encoding 参数避免乱码

7.2 异常处理 (Error Handling)

1. FileNotFoundError:文件不存在

2. PermissionError:权限不足

3. IsADirectoryError: 尝试像文件一样打开目录

4. 使用 try...except 块处理文件相关异常

7.3 文件缓冲 (File Buffering)

1. 概念理解:数据并非立即写入磁盘

2. file.flush():手动将缓冲区内容写入磁盘

7.4 临时文件和目录:tempfile 模块

  • tempfile模块是Python标准库中用于创建临时文件和目录的安全、跨平台的方式。它能自动处理命名冲突和权限问题,并在不再需要时(通常)自动清理这些文件,是处理临时数据的最佳选择。
python 复制代码
import tempfile

# 创建一个在关闭时会自动删除的临时文件
with tempfile.TemporaryFile(mode='w+', encoding='utf-8') as tf:
    tf.write("这是一些临时数据。\n")
    tf.seek(0) # 回到文件开头
    print(f"从临时文件中读出: {tf.read().strip()}")
# with 块结束后,文件已从磁盘上消失

# 创建一个有名字的临时文件,在with结束后不会自动删除
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', delete=False, suffix='.tmp') as ntf:
    print(f"创建了带名字的临时文件: {ntf.name}")
    ntf.write("持久化的临时数据")
# ntf.name 可以在后续代码中使用,但需要你手动删除
# import os; os.remove(ntf.name)

7.5 内存中的文件:io.StringIOio.BytesIO

  • io.StringIO和io.BytesIO让你能够创建"内存中的文件"。它们提供了与真实文件对象相同的接口(如.read(), .write(), .seek()),但所有操作都在内存中进行,不涉及任何磁盘I/O。StringIO处理文本(字符串),而BytesIO处理二进制数据(字节)。这在当你需要与一个只接受文件对象的API交互,但你的数据却在字符串或字节序列中时非常有用。

  • 这直接对应于Java的StringReader/StringWriter和ByteArrayInputStream/ByteArrayOutputStream。

python 复制代码
import io
import csv

# 假设一个函数需要一个文件对象来写入CSV
def write_csv_to_file_object(file_obj, data):
    writer = csv.writer(file_obj)
    writer.writerows(data)

data = [['Name', 'Age'], ['Alice', 30], ['Bob', 25]]

# 我们不想创建真实文件,而是想直接得到CSV格式的字符串
string_buffer = io.StringIO()
write_csv_to_file_object(string_buffer, data)

# 从缓冲区获取完整的字符串值
csv_string = string_buffer.getvalue()
print("---内存中生成的CSV字符串---")
print(csv_string)
python 复制代码
import io
from PIL import Image # 需要安装Pillow库: pip install Pillow

# 创建一个简单的黑色图片
img = Image.new('RGB', (60, 30), color = 'black')

# 假设一个API需要将图片"保存"到一个二进制文件流
byte_buffer = io.BytesIO()
img.save(byte_buffer, format='PNG')

# 获取图片的二进制数据,可以用于网络传输等
image_bytes = byte_buffer.getvalue()
print(f"内存中生成的PNG图片字节数: {len(image_bytes)}")

总结

文件就到这里了,后面可以根据自己的需求一边写一边学,这些都是基础的,接下来我们要学习的是:线程、进程、队列

相关推荐
循环过三天2 小时前
7.5、Python-匿名函数lambda
笔记·python·学习
大大水瓶2 小时前
Nginx学习
学习·nginx·dubbo
仟濹2 小时前
【Java 基础】3 面向对象 - this
java·开发语言·python
Dxy12393102162 小时前
Python一个类的特殊方法有哪些
开发语言·python
梅花143 小时前
基于Django的博客系统
后端·python·django·毕业设计·博客·博客系统·毕设
jiushun_suanli3 小时前
FPGA(现场可编程门阵列)详解
经验分享·学习·fpga开发
烤汉堡4 小时前
Python入门到实战:网络请求与数据获取
python
rimoyee4 小时前
[python探本] 内存数据存储机制
python
LiLiYuan.4 小时前
Arrays类和List接口的关联
java·开发语言·windows·python