全部版块 我的主页
论坛 数据科学与人工智能 人工智能
72 0
2025-11-24

人工智能之数据分析:NumPy 数据持久化详解

引言:在数据处理过程中,NumPy 提供了多种高效的数据保存与加载机制,统称为数据持久化。这些方法适用于不同场景,如数据规模、结构复杂度以及是否需要跨平台兼容等。本文将系统介绍以下几种主要方式:

  • 基础二进制和文本格式读写(np.save/np.load 等)
  • 结构化数组的定义与存储
  • 记录数组的基本用法
  • 内存映射技术用于超大文件处理
  • 其他扩展性读写方案(如 HDF5、Pickle)
.npy

.npz

.txt

一、基本数据持久化方法

1. 使用 np.savenp.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.saveznp.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.savetxtnp.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.savenp.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.name

arr['name']

np.rec.array

np.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')

六、不同场景下的格式选择建议

使用场景推荐格式主要理由
单个数组,追求快速读写
.npy
速度最快,操作最简单
多个数组打包存储
.npz
可在单一文件中整合多个数组
小规模数据,需人工查看
.txt
/
.csv
文本可读,兼容 Excel 等工具
异构字段(如姓名、年龄混合)结构化数组 +
.npy
保留字段语义信息
数组尺寸超过内存限制
np.memmap
HDF5
支持按需加载与内存映射
科学数据共享与长期存档
HDF5
跨平台、支持压缩和元数据记录
需与 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 效率,同时保持良好的跨平台兼容性。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群