人工智能之数据分析:NumPy 数据持久化详解
引言:在数据处理过程中,NumPy 提供了多种高效的数据保存与加载机制,统称为数据持久化。这些方法适用于不同场景,如数据规模、结构复杂度以及是否需要跨平台兼容等。本文将系统介绍以下几种主要方式:
- 基础二进制和文本格式读写(
np.save/np.load等) - 结构化数组的定义与存储
- 记录数组的基本用法
- 内存映射技术用于超大文件处理
- 其他扩展性读写方案(如 HDF5、Pickle)
.npy.npz.txt
一、基本数据持久化方法
1. 使用 np.save 和 np.load —— 单数组存储(推荐方式)
该方法将数组以 NumPy 专用的二进制格式保存为 .npy 文件,能够完整保留数组的类型(dtype)、维度、形状及字节序信息。
import numpy as np
# 创建并保存数组
arr = np.array([1, 2, 3, 4])
np.save('data.npy', arr)
# 加载数组
loaded = np.load('data.npy')
print(loaded) # 输出: [1 2 3 4]
优势特点:
- 基于 C 层级 I/O,读写速度极快
- 支持任意维度和数据类型
- 自动保留所有元数据,无需额外配置
np.save()np.load().npy
2. 使用 np.savez 与 np.savez_compressed —— 多数组打包存储
当需要同时保存多个数组时,可使用 np.savez 将其整合到一个 .npz 文件中。此文件本质上是 ZIP 压缩包,支持命名访问。
a = np.array([1, 2, 3])
b = np.array([[4, 5], [6, 7]])
# 无压缩保存
np.savez('arrays.npz', a=a, b=b)
# 启用压缩以节省空间
np.savez_compressed('arrays_compressed.npz', a=a, b=b)
# 加载并读取
data = np.load('arrays.npz')
print(data['a']) # [1 2 3]
print(data['b']) # [[4 5] [6 7]]
data.close() # 推荐显式关闭资源,或使用 with 上下文管理
提示:.npz 文件可用普通解压工具打开查看内部包含的各个数组文件。
np.savez()np.savez_compressed().npz
3. 文本格式操作:np.savetxt 与 np.loadtxt
适用于小规模、需人工查看或与其他系统交换的数据,例如 CSV 类型的表格数据。
arr = np.array([[1.1, 2.2], [3.3, 4.4]])
# 保存为文本,指定分隔符和浮点精度
np.savetxt('data.txt', arr, delimiter=',', fmt='%.2f')
# 从文本加载
loaded = np.loadtxt('data.txt', delimiter=',')
局限性:
- 仅支持一维或二维数组
- 浮点数可能因格式化而损失精度
- 读写效率较低,生成文件体积较大
np.savetxt()np.loadtxt()
二、结构化数组(Structured Arrays)—— 存储异构数据
当数据由不同类型字段组成(如姓名、年龄、成绩),可使用结构化数组进行组织与存储。
1. 定义复合数据类型
# 自定义数据结构
dt = np.dtype([
('name', 'U10'), # 最长10字符的Unicode字符串
('age', 'i4'), # 32位整型
('score', 'f4') # 32位单精度浮点
])
# 构建数据集
students = np.array([
('Alice', 20, 85.5),
('Bob', 22, 90.0)
], dtype=dt)
print(students['name']) # ['Alice' 'Bob']
print(students[0]) # 第一条完整记录
2. 结构化数组的持久化
结构化数组可以直接通过 np.save 和 np.load 进行保存与恢复,字段结构完全保留。
# 保存
np.save('students.npy', students)
# 加载
loaded_students = np.load('students.npy')
print(loaded_students.dtype) # 输出原始定义的 dtype,保持一致
这意味着整个数据结构包括字段名、类型和顺序均可完整重建。
.npy
三、记录数组(Record Arrays)—— 支持属性式访问
记录数组是结构化数组的一个子类,允许通过属性名直接访问字段(如 arr.name),提升代码可读性。
# 从已有结构化数组创建记录数组
rec_arr = np.rec.array(students)
# 或直接构造
rec_arr2 = np.rec.array([
('Charlie', 21, 88.0),
('Diana', 19, 92.5)
], dtype=dt)
# 属性方式访问
print(rec_arr.name) # ['Alice' 'Bob']
print(rec_arr.age) # [20 22]
注意事项:
np.rec.array是结构化数组的封装形式- 官方文档建议新项目优先使用结构化数组配合字典式访问(
arr['field']) - 记录数组虽方便,但不推荐在生产环境中广泛使用
arr.namearr['name']np.rec.arraynp.ndarray
尽管如此,记录数组仍支持标准的 np.save / np.load 操作,具备完整的持久化能力。
np.save()四、内存映射(Memory Mapping)—— 针对超大规模数组的高效处理
当需要处理远超内存容量的大型数组时(例如几十 GB 的科学计算数据),可通过 内存映射(memmap) 技术实现按需加载,避免一次性将整个文件载入内存。
其核心原理如下:
- 原始数据存储于磁盘文件中
- NumPy 利用操作系统的虚拟内存机制,将该文件“映射”为一个数组对象
- 在访问数组特定区域时,系统自动从磁盘读取对应的数据块
- 整个过程无需预先加载全部内容到内存
np.memmap
# 创建一个约 10GB 的 float32 类型数组(仅占用磁盘空间)
filename = 'big_array.dat'
shape = (1000000, 2500) # 约 10^10 个元素 × 每个 4 字节 ≈ 37.25 GB
# 初始化 memmap 对象(mode='w+' 表示可读写,若文件不存在则创建)
big_arr = np.memmap(filename, dtype='float32', mode='w+', shape=shape)
# 可选:初始化数值(实际写入磁盘,内存使用极少)
big_arr[:] = 0.0
# 正常使用,如同普通 NumPy 数组
big_arr[0, :10] = np.arange(10)
# 将修改强制写回磁盘(关键步骤!)
big_arr.flush()
# 后续可重新加载该文件
loaded = np.memmap(filename, dtype='float32', mode='r', shape=shape)
print(loaded[0, :10]) # 输出: [0. 1. 2. ... 9.]
常见模式说明:
| mode | 说明 |
|---|---|
| r | 只读模式 |
| r+ | 读写模式(文件必须已存在) |
| w+ | 读写模式,若文件存在则覆盖,否则新建 |
| c | 复制写模式,修改不会影响原始文件 |
'r'
'r+'
'w+'
'c'
典型应用场景包括:
- 卫星遥感图像处理
- 基因组序列数据分析
- 物理仿真中的状态快照
- 大规模嵌入向量矩阵操作
五、其他常用数据读写方式
1. Pickle —— Python 原生序列化工具
适用于保存包含 NumPy 数组在内的复杂 Python 对象(如字典、类实例等)。
import pickle
# 保存对象
with open('data.pkl', 'wb') as f:
pickle.dump(arr, f)
# 加载对象
with open('data.pkl', 'rb') as f:
loaded = pickle.load(f)
局限性:
- 不具备跨语言兼容性
- 存在安全风险(反序列化可能执行恶意代码)
- 性能较低,文件体积通常大于 np.save 格式
.npy
因此,建议仅用于临时保存或调试用途,特别是涉及非纯数组结构的复合对象。
2. HDF5 —— 推荐用于大型科学数据存储
借助 h5py 库支持,HDF5 是处理大规模、多维、分层数据的理想选择。
h5py
import h5py
# 写入数据
with h5py.File('data.h5', 'w') as f:
f.create_dataset('my_array', data=arr)
f.create_dataset('students', data=students) # 支持结构化数组
# 读取数据
with h5py.File('data.h5', 'r') as f:
arr_h5 = f['my_array'][:] # 全量加载
partial = f['my_array'][0:10] # 支持切片,按需读取部分数据
HDF5 的优势:
- 支持 TB 级别的超大文件
- 具备类似文件夹的层级结构组织能力
- 内置压缩功能(支持 gzip、lzf 等算法)
- 跨平台且支持多种编程语言(C/Fortran/Python/Matlab)
- 支持部分读取,性能接近内存映射
安装命令:
pip install h5py
3. Parquet / Feather —— 表格型数据专用格式
对于表格形式的数据(如结构化数组或 DataFrame),推荐结合 Pandas 使用列式存储格式。
import pandas as pd
# 将结构化数组转为 DataFrame
df = pd.DataFrame(students)
# 保存为 Parquet(高效压缩、适合大数据)
df.to_parquet('data.parquet')
# 读取恢复
df_loaded = pd.read_parquet('data.parquet')
六、不同场景下的格式选择建议
| 使用场景 | 推荐格式 | 主要理由 |
|---|---|---|
| 单个数组,追求快速读写 | | 速度最快,操作最简单 |
| 多个数组打包存储 | | 可在单一文件中整合多个数组 |
| 小规模数据,需人工查看 | / | 文本可读,兼容 Excel 等工具 |
| 异构字段(如姓名、年龄混合) | 结构化数组 + | 保留字段语义信息 |
| 数组尺寸超过内存限制 | 或 | 支持按需加载与内存映射 |
| 科学数据共享与长期存档 | | 跨平台、支持压缩和元数据记录 |
| 需与 Pandas 生态交互 | Parquet / HDF5 | 无缝对接 DataFrame 操作流程 |
七、重要注意事项
路径与编码问题
- Windows 系统中应优先使用正斜杠或 os.path.join() 处理路径分隔符
- 涉及文本文件时需注意字符编码一致性(如 UTF-8)
pathlib
encoding='utf-8'
版本兼容性
- .npy 和 .npz 格式随 NumPy 版本更新而演进,但具备良好的向后兼容性
.npy
内存映射对象的生命周期管理
- 务必保持对 memmap 对象的引用,防止被 Python 垃圾回收提前释放
- 任何修改后都应调用 .flush() 方法确保变更持久化到磁盘
memmap
.flush()
结构化数组中字符串字段长度限制
- 定义结构化数组时,字符串字段需显式指定最大长度(如 'U20')
- 超出长度的内容会被截断
'U10'
np.load()掌握这些持久化技术,我们能够灵活应对从 KB 到 TB 级别的数据存储需求。NumPy 的设计哲学在于:小规模数据采用
.npy
进行处理,大规模数据则推荐使用
memmap
或
HDF5
而对于结构化数据,则建议通过自定义 dtype 来实现高效管理。
在处理 Unicode 字符时,系统最多支持 10 个字符,超出部分将被截断。例如:?!这类符号也需遵循此规则,确保数据的一致性与完整性。
本文重点介绍了 NumPy 中的数据持久化方法。相关 Python 过渡项目的部分代码已托管至 Gitee 平台,并将持续更新,进度主要受限于开发时间。用户可自行下载源码用于本地学习与功能扩展。
关于数据存储方案的选择,应根据实际场景权衡性能与可维护性。对于数值计算密集型应用,结合 NumPy 的内存映射和文件保存机制(如 .npy 或 .npz 格式),可以显著提升 I/O 效率,同时保持良好的跨平台兼容性。

雷达卡


京公网安备 11010802022788号







