前言

在科研与工程实践中,MATLAB 的 .mat 文件是不同语言与工具之间交换矩阵、结构体、cell 等数据的常见格式。随着 MATLAB 在 2006 年引入基于 HDF5 的 -v7.3 格式(从 R2006b / MATLAB 7.3 起可用),Python 端处理 .mat 文件出现了两类截然不同的工作流:一类是“经典” MAT-file(v7 / v7.2 / 更早)适合用 scipy.io.loadmat;另一类是 HDF5(v7.3)适合用 h5py(或专门库 hdf5storagemat73 等)。本文详细探讨了这两种工作流的区别、适用场景和潜在挑战,附实用代码片段与检查清单,方便科研/工程日常使用。


一、 MAT-file 版本快速概览

  • v7.3(HDF5)格式:从 MATLAB v7.3 = Release R2006b 开始支持(可用 save(...,'-v7.3') 保存)。本质上是 HDF5 文件,适合保存 >2GB 的单变量并支持按需读取。
  • v7.2 及之前(经典 MAT):对应 R2006a 及更早版本生成的“经典” MAT 格式,scipy.io.loadmat 对这类格式支持最好。
  • 默认格式:Matlab R2019b及之前版本默认为-v7.3格式,Matlab R2020a及之后版本虽然也默认为 -v7.3 格式,但也支持之前的格式。如果你希望明确地指定保存为-v7.3格式,可以在save函数中使用-v7.3选项。例如:
save('filename.mat', 'variableName', '-v7.3');

二、 如何判断 .mat 文件是 v7.3(HDF5)还是经典格式

最简单也是最可靠的方法是读文件看开头(HDF5 文件以 \x89HDF 开头):

def is_mat_v73(path):
    with open(path,'rb') as f:
        head = f.read(8)
    return head.startswith(b'\x89HDF')
  • 如果返回 True:通常就是 v7.3(HDF5)。推荐用 h5pyhdf5storage 来读取。
  • 如果返回 False:可能是经典 MAT(v7/v7.2/更早),可以用 scipy.io.loadmat 读取(更方便地将 MATLAB struct/cell 映射到 Python 对象/列表)。

三、scipy.io.loadmat:什么时候用、常用参数与注意事项

适用场景:经典 MAT(v7 / v7.2 及更早)且数据量不会导致内存溢出时。

示例:

from scipy.io import loadmat
data = loadmat('file.mat',
               squeeze_me=True,        # 去掉单例维度
               struct_as_record=False,  # 用更易用的对象形式返回 struct
               simplify_cells=True)     # 尽量把 cell 转为 list/ndarray(较新 SciPy 支持)
# 访问变量
X = data['X']

常见参数与作用

  • squeeze_me=True:压掉多余维度(如 (1, N, 1))。
  • struct_as_record=False:把 MATLAB struct 转成更可读的对象/嵌套结构,而不是 numpy record。
  • simplify_cells=True:(较新 SciPy)把 cell 数组尽量简化成 Python list/ndarray。

限制

  • 不能读取 v7.3 文件 —— 会抛出提示要求用 HDF5 读器。
  • 一次性把整个文件加载到内存中,遇大文件(尤其大数组)会内存压力很大。

四、h5py:什么时候用、基本用法与陷阱

适用场景.mat 是 v7.3(HDF5)或你需要按需读取非常大的数据集时。

示例:

import h5py
with h5py.File('file.mat', 'r') as f:
    print(list(f.keys()))       # 顶层变量名(注意:MATLAB 在 HDF5 中的存储结构和命名风格)
    dset = f['myVar']
    print(dset.shape, dset.dtype)
    arr = dset[:]               # 一次性读取为 numpy array
    part = dset[:, :100]        # 切片读取(按需)

注意事项与常见坑

  • MATLAB 在 HDF5 中保存字符串、char、cell、对象引用等时使用的内部约定会使 h5py 直接读取后返回 bytes、引用或复杂结构,需要额外解码/解析。
  • 直接用 h5py 写入或改写 v7.3 .mat 很容易破坏 MATLAB 能读回的元信息(MATLAB 期望特定的属性/元数据)。因此建议写 v7.3 文件时优先使用 hdf5storage 或 MATLAB 自身的 save -v7.3,而不是手动用 h5py 构造复杂结构。
  • 若只读并需要兼容 MATLAB 的高层语义(比如 cell、struct、对象):hdf5storage 通常比裸 h5py 更方便,因为它做了 MATLAB <-> HDF5 的映射与编码处理。

五、从 Python 写回 .mat:推荐做法

  • 写经典 MAT(v5/v7 等):使用 scipy.io.savemat,例如 savemat('out.mat', {'X': arr}, format='5')。这对于与老版本 MATLAB 或需要最大兼容性的场景通常最稳妥。
  • 写 v7.3(HDF5):如果目标是让 MATLAB 用户能顺利打开结果文件,优先方法是让 MATLAB 来生成(save(...,'-v7.3')),或在 Python 端使用 hdf5storage(一个基于 h5py 的高层库,它会写入 MATLAB 需要的元信息)。裸用 h5py 写 v7.3 在很多情况下会导致 MATLAB 无法读取或读出错误的对象类型。

五、常见坑及对应解决办法(汇总)

  1. 报错 Please use HDF reader for matlab v7.3 files
    → 检测文件是否为 HDF5(见上方 is_mat_v73),改用 h5pyhdf5storage / mat73

  2. struct / cell 读取后嵌套难以直接使用
    loadmat(..., struct_as_record=False, squeeze_me=True, simplify_cells=True);或在用 h5py 时手动解析 HDF5 结构引用并转换为 dict/list。

  3. 字符串显示为 bytes 或编码混乱
    → 对字节进行 decode('utf-8')(或视具体编码),或用 hdf5storage 让库处理编码。注意:有时 MATLAB 在 v7.3 中会用 UTF-16 编码,需对应 decode。

  4. 维度/行列顺序问题(MATLAB 为列主)
    → 多数情况下 NumPy 会得到正确的形状,但在 reshape/flatten 或跨语言序列化时应注意使用 order='F' 或用 .T 做转置。

  5. 内存不足
    loadmat 会一次性载入整个文件;若文件非常大且是 v7.3,优先用 h5py 做按需切片读取。


六、实用工作流(推荐)

  • 我有一个 .mat 文件,不知道版本

    1. 先用 is_mat_v73(path) 检测头。
    2. 如果是 v7.3(HDF5)→ 用 h5py 展开 list(f.keys()),按需读取需要的数据集;若想得到 MATLAB 风格的 high-level object(cell/struct),用 hdf5storage 或手写解析逻辑。
    3. 如果不是 v7.3 → 用 scipy.io.loadmat(..., squeeze_me=True, struct_as_record=False, simplify_cells=True)
  • 我要把 Python 数据写给 MATLAB 用户读

    • 若文件尺寸较小且简单(数组、基本结构):用 scipy.io.savemat 写经典 MAT。
    • 若确实需要 v7.3(比如单变量 > 2GB):在 MATLAB 里保存 -v7.3,或者在 Python 用 hdf5storage(慎用裸 h5py 写复杂对象)。

七、常用代码片段合集(可直接拷贝)

检测是否 v7.3(HDF5)

def is_mat_v73(path):
    with open(path,'rb') as f:
        head = f.read(8)
    return head.startswith(b'\x89HDF')

scipy.io.loadmat 读取经典 MAT

from scipy.io import loadmat
data = loadmat('file.mat', squeeze_me=True, struct_as_record=False, simplify_cells=True)
# 访问:
# X = data['X']
# some_struct = data['myStruct'].fieldname  # 视 loadmat 返回类型而定

h5py 读取 v7.3(HDF5)

import h5py
with h5py.File('file.mat', 'r') as f:
    print(list(f.keys()))
    dset = f['myVar']
    print(dset.shape, dset.dtype)
    arr = dset[:]             # 读取全部
    part = dset[0:100, :]     # 分块读取以节省内存

把 bytes 解码为字符串

import numpy as np
# 假设 arr 是 dtype 'S' 或含 bytes 的数组
strs = np.array([x.decode('utf-8') for x in arr.ravel()]).reshape(arr.shape)

总结

  • 判断 .mat 文件版本是关键:v7.3 = HDF5(从 R2006b / MATLAB 7.3 起可写),之前的通常是经典 MAT(可用 scipy.io.loadmat 处理)。用文件头判断是最简单的办法。
  • 对于经典 MAT(v7 / v7.2 / 更早)scipy.io.loadmat 最方便(带若干参数可改善 struct/cell 的表现);但它会将整个文件一次性读入内存。
  • 对于v7.3(HDF5)h5py 是阅读的大杀器——支持按需读取 / 切片,从而处理非常大的数据集;但直接写入 v7.3(尤其复杂对象)用 h5py 很容易出错,优先选择 hdf5storage 或让 MATLAB 来写入 -v7.3
  • 遇到 struct/cell/字符串或对象引用问题时,hdf5storage 常常比裸 h5py 更省力;而 scipy.io.loadmat 的参数(squeeze_me, struct_as_record=False, simplify_cells=True)能显著改善经典 MAT 的可用性。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐