MATLAB

save 和 load 数据持久化:save data.mat A B 存工作区,load 一键恢复

👤 为我痴狂 👁 2 阅读 ❤ 0 点赞 ➦ 0 分享 📅 2026-10-11
首页› 理学› MATLAB› 正文
save 和 load 数据持久化:save data.mat A B 存工作区,load 一键恢复

从命令行语法到 MAT 二进制格式内核 · 从工程实践到跨语言互操作 · 一条主线贯通数据持久化的完整技术栈

摘要

MATLAB 的 save 与 load 是科学计算中最常用、也最容易被低估的一对命令。表面看,save data.mat A B 只是把变量 A、B 写入磁盘,load 只是读回来;但在这条极简接口之下,隐藏着 MAT 文件格式三十余年的版本演进、压缩编码策略、内存映射机制、对象序列化协议以及跨语言互操作规范。本文以“持久化边界”为贯穿全文的分析主线——即数据在内存表示与磁盘表示之间转换时,哪些信息被保留、哪些被丢弃、哪些被重新解释——系统梳理 save/load 的语法矩阵、MAT 格式内核、性能工程实践、对象与句柄的序列化陷阱、HDF5 互操作以及大规模数据场景下的替代方案。全文结合 MathWorks 官方文档、MAT 格式逆向工程文献与近三年相关研究,给出可落地的操作路径与选型决策框架,并对数据持久化的前沿方向作出研判。

第一章 持久化边界:一条被忽视的分析主线

任何数据持久化系统都必须回答同一个问题:内存中的对象,在写入磁盘再读回之后,还是不是同一个对象?这个问题的答案,取决于系统在“内存表示”与“磁盘表示”之间划定的边界。边界之内,信息完整保留;边界之外,信息被丢弃或重新解释。MATLAB 的 save/load 正是这样一套边界系统,而理解这条边界,比记住语法重要得多。

举一个最直观的例子。在 MATLAB 命令窗口执行以下代码:

% 构造一个简单结构体
s.name = 'sensor_A';
s.value = 42;
s.timestamp = datetime('now');

% 保存到 MAT 文件
save('demo.mat', 's');

% 清除工作区变量
clear s;

% 重新加载
load('demo.mat');

% 检查类型
whos s

执行 whos s 后,你会看到 s 的类型仍然是 struct,字段完整,datetime 对象也正确恢复。看起来边界是“透明”的。但如果我们把 s 换成一个 containers.Map 对象,或者一个自定义类的实例,情况就完全不同了。

笔者认为,持久化边界的本质是一组“可序列化契约”。MATLAB 在 save 时,会按照一套内部规则将对象编码为字节流;load 时再按照同一套规则解码。凡是这套规则覆盖的类型,边界透明;凡是规则未覆盖或覆盖不完整的类型,边界就会产生信息损失。这条主线将贯穿本文所有章节——无论是讨论 MAT 格式版本差异、压缩策略,还是讨论对象序列化和跨语言互操作,本质上都是在讨论“边界画在哪里、画得是否合理”。

本文评述:把 save/load 当作“黑箱命令”来用,在小型脚本中问题不大;但一旦进入工程化场景——比如需要长期归档实验数据、需要在 Python 与 MATLAB 之间交换中间结果、需要保存包含句柄引用的复杂对象图——边界问题就会集中暴露。本文后续章节将逐层拆解这条边界,并给出可操作的应对策略。

第二章 save/load 语法矩阵与工程用法

2.1 基本语法与变量选择

save 的最简形式是 save filename,它会将当前工作区的所有变量写入名为 filename.mat 的文件。但工程实践中,更常见的是显式指定变量列表:

% 保存指定变量
save('data.mat', 'A', 'B');

% 使用通配符保存匹配变量
save('data.mat', 'sensor_*');

% 保存整个工作区(显式写法)
save('data.mat');

% 保存为特定版本格式
save('data_v7.mat', 'A', 'B', '-v7');

% 保存为 HDF5 格式(v7.3)
save('data_v73.mat', 'A', 'B', '-v7.3');

这里有一个容易被忽略的细节:save 的变量名参数是字符串,而不是变量本身。也就是说,save('data.mat', 'A') 保存的是名为 A 的变量,而不是变量 A 的值。这个设计在动态生成变量名的场景下非常有用,但也容易在重构时引入 bug——如果变量名拼写错误,MATLAB 不会报错,而是静默地不保存任何东西(或保存一个空文件)。

本文评述:这种“字符串寻址”机制在 R2020b 之后有了改进,save 在未找到匹配变量时会发出警告。但在更早版本中,静默失败是常见陷阱。工程上建议始终使用 exist 或 who 先做一次检查,或者改用 save 的函数形式配合 matfile 对象进行更精细的控制。

2.2 load 的三种调用形式

load 的调用形式比 save 更丰富,主要分为三类:

调用形式 行为 适用场景
load('data.mat') 将所有变量加载到当前工作区 交互式探索、快速恢复
S = load('data.mat') 返回结构体 S,字段名对应变量名 函数内部使用、避免污染工作区
load('data.mat', 'A', 'B') 仅加载指定变量 大文件中部分读取、节省内存

第二种形式 S = load(...) 在工程代码中尤其值得推荐。它把加载结果封装在一个结构体中,避免了变量名冲突,也让数据流更加清晰。例如:

function result = processExperiment(matFile)
    % 以结构体形式加载,避免污染调用者工作区
    data = load(matFile);
    
    % 显式访问字段
    signal = data.signal;
    fs = data.sampleRate;
    
    % 处理逻辑...
    result = computeSpectrum(signal, fs);
end

笔者认为,这种“结构体返回”模式是 MATLAB 函数式编程风格的一个缩影。它把“加载”这个副作用操作转化为一个纯函数调用,返回值就是数据本身。相比之下,直接 load('data.mat') 会把变量注入当前作用域,在大型项目中极易造成命名空间污染。MathWorks 官方文档也建议在函数内部使用结构体形式。

2.3 部分加载与内存映射

当 MAT 文件体积达到数 GB 时,一次性加载所有变量可能耗尽内存。MATLAB 提供了两种应对策略:部分加载和内存映射。

部分加载通过 load(filename, 'var1', 'var2') 实现,只读取指定变量。但需要注意的是,对于 v7 及更早版本的 MAT 文件,部分加载仍然需要解析整个文件头,只是跳过了不需要的变量数据块。对于 v7.3(HDF5 格式),部分加载则可以利用 HDF5 的索引结构实现真正的按需读取。

内存映射则通过 matfile 对象实现:

% 创建 matfile 对象,不立即加载数据
m = matfile('largeData.mat');

% 查看文件中有哪些变量
whos(m)

% 按需读取部分数据
partialData = m.signal(1:1000, :);

% 修改并保存(仅支持 v7.3 格式)
m.signal(1:1000, :) = zeros(1000, size(m.signal, 2));

matfile 对象的强大之处在于,它把 MAT 文件当作一个“磁盘上的数组”来对待。读取 m.signal(1:1000, :) 时,MATLAB 只会从磁盘读取对应的数据块,而不是整个 signal 变量。这对于处理超出内存容量的大型矩阵尤其有用。

本文评述:但 matfile 并非万能。它要求文件必须是 v7.3 格式(即 HDF5 底层),且对变量类型的支持有限——结构体、元胞数组、对象等复杂类型的部分读写支持并不完整。此外,频繁的小块读写会带来显著的 I/O 开销,因为每次访问都可能触发一次磁盘寻道。工程上建议将 matfile 用于“大矩阵、少变量”的场景,而不是“小变量、多字段”的结构体。

第三章 MAT 文件格式内核:从 v4 到 v7.3

3.1 版本演进与格式差异

MAT 文件格式并非一成不变。从 MATLAB 4.0 到 R2024b,MAT 格式经历了多次重大修订。理解这些版本差异,是排查兼容性问题的前提。

版本 引入时间 底层格式 关键特性
v4 MATLAB 4 自定义二进制 仅支持二维双精度矩阵和字符数组
v6 MATLAB 5 自定义二进制 支持多维数组、结构体、元胞数组
v7 MATLAB 7.0 自定义二进制 + zlib 压缩 默认压缩、支持 Unicode 变量名
v7.3 MATLAB 7.3 HDF5 支持 >2GB 文件、部分加载、跨语言

v4 和 v6 格式的差异,最直观的体现是文件头。v4 格式的文件头只有 20 字节,包含类型、行数、列数等信息;v6 格式则引入了 128 字节的文件头,包含版本号、字节序、描述字符串等元数据。v7 在 v6 基础上增加了压缩标志,v7.3 则完全转向 HDF5 容器。

笔者认为,MAT 格式的演进史,本质上是一部“持久化边界不断扩张”的历史。v4 只能保存数值矩阵,边界极窄;v6 把结构体和元胞数组纳入边界;v7 增加了压缩,让边界内的数据更紧凑;v7.3 则通过 HDF5 把边界扩展到跨语言、跨平台、超大规模。每一次版本升级,都是在回答“我们还能把什么信息安全地写入磁盘”。

3.2 MAT 文件的二进制结构

以 v7 格式为例,一个 MAT 文件的典型结构如下:

+-----------------------------+
| 128 字节文件头                |
|  - 描述字符串 (116 字节)      |
|  - 版本号 (2 字节)            |
|  - 字节序标识 (2 字节)        |
+-----------------------------+
| 数据元素 1                    |
|  - 类型标签 (8 字节)          |
|  - 数据长度 (4 字节)          |
|  - 数据内容 (变长)            |
+-----------------------------+
| 数据元素 2                    |
|  ...                         |
+-----------------------------+

每个数据元素(data element)由三部分组成:类型标签(miType)、字节数(miBytes)和实际数据。类型标签是一个 32 位整数,高 16 位表示数据类型(如 miDOUBLE、miINT32、miMATRIX),低 16 位表示数据字节数(如果小于 4 字节)。对于矩阵类型(miMATRIX),数据部分又嵌套了子元素,分别描述数组的维度、名称和实际数值。

这种嵌套结构使得 MAT 文件具有很好的自描述性。即使没有 MATLAB,第三方工具也可以按照格式规范解析出变量名、维度、类型和数值。SciPy 的 scipy.io.loadmat 就是基于这套规范实现的。

本文评述:但自描述性也带来了开销。对于大量小变量(比如 1000 个 1×1 的 double),每个变量都需要一个完整的类型标签和维度描述,元数据开销可能超过数据本身。这也是为什么在工程中,把大量小变量打包成一个结构体或元胞数组再保存,通常比逐个保存更高效。

3.3 v7.3 与 HDF5 的映射关系

v7.3 格式的 MAT 文件本质上是一个 HDF5 文件。MATLAB 在 HDF5 之上定义了一套映射规则:

  • 每个 MATLAB 变量对应 HDF5 中的一个数据集(dataset)或组(group)
  • 数值数组直接映射为 HDF5 数据集,维度对应 HDF5 的 dataspace
  • 结构体映射为 HDF5 组,字段名对应组内的数据集名
  • 元胞数组映射为 HDF5 组,每个元素对应一个数据集
  • 对象(classdef 实例)映射为包含特殊属性的 HDF5 组

这种映射关系意味着,任何支持 HDF5 的工具(如 Python 的 h5py、C 的 HDF5 库、Julia 的 HDF5.jl)都可以读取 v7.3 格式的 MAT 文件,而不需要依赖 MATLAB。这极大地扩展了持久化边界——数据不再被锁定在 MATLAB 生态内。

笔者认为,v7.3 的 HDF5 转向是 MAT 格式历史上最具战略意义的一步。它把 MATLAB 从“自有格式的封闭王国”变成了“开放格式的参与者”。但代价是,v7.3 文件通常比 v7 文件更大(HDF5 的元数据开销更高),且压缩效率可能不如 v7 的 zlib 压缩。因此,选择 v7 还是 v7.3,本质上是在“兼容性”和“体积”之间做权衡。

第四章 性能工程:压缩、内存映射与分块策略

4.1 压缩的代价与收益

从 v7 开始,MATLAB 默认对 MAT 文件进行 zlib 压缩。压缩可以显著减小文件体积,但也会增加保存和加载的 CPU 时间。以下是一组基于模拟数据的对比测试(数据来源:本文作者在 MATLAB R2023b 环境下使用 rand(5000) 生成的 5000×5000 双精度矩阵,重复 10 次取平均值,模拟数据):

格式 文件大小 保存耗时 加载耗时
v6(无压缩) 190.7 MB 0.42 s 0.31 s
v7(zlib 压缩) 190.5 MB 1.87 s 0.95 s
v7.3(HDF5) 190.8 MB 0.68 s 0.52 s

注意:随机矩阵的熵很高,zlib 几乎无法压缩,因此 v7 和 v6 的文件大小几乎相同,但 v7 的保存耗时是 v6 的 4 倍以上。如果换成高度结构化的数据(如稀疏矩阵、重复模式的数据),压缩收益会显著得多。

本文评述:很多工程师习惯性地使用默认的 v7 格式,却不知道压缩在某些场景下是纯粹的负担。对于已经压缩过的数据(如 JPEG 图像、HDF5 内部已压缩的数据集),再套一层 zlib 几乎没有收益,只会浪费 CPU。工程建议是:如果数据熵高、且磁盘空间不是瓶颈,可以用 save(..., '-v6') 跳过压缩;如果数据熵低、或需要长期归档,则用 v7 压缩。

4.2 分块保存与追加写入

在流式数据处理场景中,数据是逐步产生的,不可能等到全部生成后再一次性保存。这时需要分块保存策略。MATLAB 提供了两种思路:

思路一:使用 matfile 对象进行追加写入。这种方式要求文件是 v7.3 格式,且写入的变量维度必须与已有变量兼容。

% 初始化一个空的 v7.3 MAT 文件
m = matfile('stream.mat', 'Writable', true);

% 预分配一个足够大的矩阵
m.data = zeros(1000000, 1);

% 分块写入
chunkSize = 10000;
for k = 1:100
    idx = (k-1)*chunkSize + 1 : k*chunkSize;
    m.data(idx, 1) = acquireChunk(k);
end

思路二:将每个数据块保存为独立的 MAT 文件,最后用脚本合并。这种方式实现简单,但会产生大量小文件,管理成本高。

笔者认为,分块保存的核心矛盾是“写入灵活性”与“读取效率”之间的权衡。追加写入虽然灵活,但 HDF5 的 chunked storage 机制会导致数据在磁盘上不连续,后续读取时随机 I/O 增加。如果数据最终会被整体读取,不如在内存中缓冲到一定规模后再一次性写入;如果数据只会被部分读取,则追加写入更合适。

4.3 并行保存与异步 I/O

在 R2020a 之后,MATLAB 的 parsave 模式(在 parfor 循环中保存数据)得到了改进。由于 save 不是线程安全的,在并行循环中直接调用会导致竞争条件。标准做法是为每个 worker 分配独立的文件:

parfor k = 1:numWorkers
    result = computeExpensive(k);
    filename = sprintf('result_%d.mat', k);
    save(filename, 'result');
end

% 后续合并
allResults = cell(1, numWorkers);
for k = 1:numWorkers
    filename = sprintf('result_%d.mat', k);
    allResults{k} = load(filename);
end

这种“分而治之”的策略虽然简单,但在 worker 数量很多时会产生大量临时文件。另一种思路是使用 parallel.pool.DataQueue 将结果汇总到客户端,由客户端统一保存。但这种方式会引入通信开销,适合结果体积较小的场景。

第五章 对象、句柄与函数句柄的序列化陷阱

5.1 值对象与句柄对象的保存差异

MATLAB 中的对象分为值对象(value class)和句柄对象(handle class)。值对象在赋值时复制,句柄对象在赋值时共享引用。这种语义差异在保存和加载时会带来截然不同的行为。

对于值对象,save 会保存其所有属性值,load 时会重新构造一个等价的对象。对于句柄对象,save 会保存对象的状态,但 load 后得到的是一个“新”的句柄对象,原有的引用关系(如多个变量指向同一对象)会丢失。

% 定义一个句柄类
classdef Counter < handle
    properties
        Count = 0
    end
    methods
        function increment(obj)
            obj.Count = obj.Count + 1;
        end
    end
end

% 创建两个引用指向同一对象
a = Counter();
b = a;
a.increment();  % a.Count 和 b.Count 都是 1

% 保存
save('counter.mat', 'a', 'b');

% 清除并加载
clear a b;
load('counter.mat');

% 检查引用关系
a.increment();
disp(a.Count);  % 2
disp(b.Count);  % 2?还是 1?

在 MATLAB R2023b 中,load 后 a 和 b 会恢复为指向同一对象的两个引用,因为 MAT 格式在保存时会记录对象之间的引用关系。但这种恢复并非在所有版本和所有场景下都可靠。如果对象图中存在循环引用,或者对象引用了外部资源(如文件句柄、网络连接),保存和加载的行为就会变得复杂。

本文评述:句柄对象的持久化是一个典型的“边界模糊”问题。MATLAB 试图在保存时记录引用拓扑,但并非所有引用都能被正确恢复。工程上的安全做法是:如果对象包含外部资源引用,应该实现自定义的 saveobj 和 loadobj 方法,显式控制哪些属性被保存、哪些被重建。

5.2 saveobj 与 loadobj:自定义序列化契约

MATLAB 允许类作者通过 saveobj 和 loadobj 方法自定义序列化行为。这相当于在默认持久化边界之外,划出一条由类作者控制的“自定义边界”。

classdef FileReader < handle
    properties
        FilePath
        FileHandle  % 不可序列化的资源
        Cache       % 可重建的缓存
    end
    
    methods
        function s = saveobj(obj)
            % 只保存必要信息
            s.FilePath = obj.FilePath;
            % FileHandle 和 Cache 不保存
        end
    end
    
    methods (Static)
        function obj = loadobj(s)
            % 重建对象
            obj = FileReader(s.FilePath);
            % FileHandle 会在构造函数中重新打开
        end
    end
end

这种模式在工程中非常实用。它把“持久化边界”从 MATLAB 的默认规则,转变为类作者可以精确控制的契约。哪些信息必须保留(如文件路径),哪些信息可以重建(如缓存),哪些信息必须丢弃(如句柄),都在 saveobj/loadobj 中明确表达。

笔者认为,saveobj/loadobj 机制是 MATLAB 对象持久化中最被低估的特性。很多工程师在遇到“保存对象后加载报错”时,第一反应是放弃保存对象,改为保存结构体。但这实际上是把问题推给了下游——结构体丢失了类型信息和方法,加载后需要手动重建。正确做法是实现 saveobj/loadobj,让对象自己负责自己的持久化。

5.3 函数句柄与匿名函数的序列化

函数句柄的保存是一个更微妙的边界问题。MATLAB 可以保存函数句柄,但加载后句柄的有效性取决于被引用函数是否在路径上。

% 命名函数句柄
f = @sin;
save('func.mat', 'f');

% 匿名函数句柄
g = @(x) x.^2 + 1;
save('func.mat', 'g', '-append');

% 加载后
clear f g;
load('func.mat');
disp(f(0));  % 0,正常工作
disp(g(3));  % 10,正常工作

匿名函数句柄的保存依赖于 MATLAB 对函数体的捕获。在大多数情况下,简单的匿名函数可以正确保存和恢复。但如果匿名函数捕获了大量工作区变量,或者引用了嵌套函数中的变量,保存行为就可能不符合预期。

本文评述:函数句柄的持久化边界,本质上取决于“函数定义是否可独立于定义环境而存在”。命名函数(如 @sin)的定义在路径上,边界透明;匿名函数的定义在句柄内部,边界取决于捕获变量的可序列化性。工程建议是:尽量避免保存复杂的匿名函数句柄,如果必须保存,先用 func2str 转换为字符串,加载后再用 str2func 重建。

第六章 跨语言互操作:HDF5、Python 与 Julia

6.1 Python 读取 MAT 文件:scipy.io 与 h5py

Python 生态中读取 MAT 文件主要有两条路径:scipy.io.loadmat 用于 v7 及更早格式,h5py 用于 v7.3 格式。

# 使用 scipy.io 读取 v7 格式
import scipy.io as sio
data = sio.loadmat('data.mat')
print(data['A'].shape)

# 使用 h5py 读取 v7.3 格式
import h5py
with h5py.File('data_v73.mat', 'r') as f:
    print(list(f.keys()))
    A = f['A'][:]  # 读取为 numpy 数组

但跨语言读取并非总是无缝的。MATLAB 和 Python 在数组存储顺序上存在根本差异:MATLAB 使用列优先(column-major),Python/NumPy 默认使用行优先(row-major)。scipy.io.loadmat 会自动转置数组以匹配 NumPy 的约定,但 h5py 不会——它直接读取 HDF5 数据集的原始布局,需要用户手动处理转置。

笔者认为,这种存储顺序差异是跨语言互操作中最常见的“静默错误”来源。一个在 MATLAB 中形状为 (100, 200) 的矩阵,用 h5py 读取后可能变成 (200, 100),而程序不会报错,只是计算结果完全错误。工程上的防御性做法是:在跨语言交换数据时,始终显式记录维度顺序,并在读取后立即验证形状。

6.2 Julia 与 MAT 文件的互操作

Julia 通过 MAT.jl 包支持 MAT 文件读写。该包同时支持 v4、v6、v7 和 v7.3 格式,底层分别使用自定义解析器和 HDF5.jl。

using MAT

# 读取 v7 格式
data = matread("data.mat")
A = data["A"]

# 写入 v7.3 格式
matwrite("output.mat", Dict("result" => A), compress=true)

Julia 的数组也是列优先,因此与 MATLAB 的互操作在存储顺序上天然一致,不需要转置。这是 Julia 相对于 Python 的一个隐性优势。

6.3 跨语言互操作的最佳实践

基于以上分析,本文总结跨语言 MAT 文件交换的几条实践原则:

  1. 优先使用 v7.3 格式:HDF5 是跨语言的事实标准,工具链最成熟。
  2. 避免保存复杂对象:自定义类、句柄对象、函数句柄在跨语言场景下几乎无法正确恢复。建议在保存前转换为结构体或基本类型。
  3. 显式记录维度顺序:在文件元数据中标注数组是行优先还是列优先,或者统一在读取后转置。
  4. 用字符串代替字符数组:MATLAB 的 char 数组在 Python 中会变成 numpy 的字符串数组,而 string 类型在 v7.3 中存储为 HDF5 的变长字符串,互操作性更好。
  5. 测试边界情况:空数组、NaN、Inf、复数、稀疏矩阵在跨语言转换时都可能有特殊行为,需要单独测试。

关于跨语言数据交换的更多细节,可以参考 MathWorks 官方文档中关于 MAT 文件版本的说明(MAT-File Versions)以及 SciPy 的 IO 教程(SciPy IO Tutorial)。

第七章 大规模数据场景下的替代方案

7.1 何时不该用 MAT 文件

MAT 文件并非万能。以下场景中,继续使用 MAT 文件可能不是最优选择:

  • 超大规模数据(>100 GB):MAT 文件的元数据管理和索引结构在超大规模下效率下降,专用格式(如 Parquet、Zarr)更合适。
  • 需要列式查询:如果数据需要按列筛选、聚合,Parquet 等列式格式的查询效率远高于 MAT。
  • 多写入者并发:MAT 文件不支持并发写入,分布式场景下需要数据库或分布式文件系统。
  • 长期归档(>10 年):MAT 格式虽然稳定,但 HDF5 和 Parquet 的生态更广泛,长期可读性更有保障。

7.2 Parquet、Zarr 与 MAT 的对比

特性 MAT v7.3 Parquet Zarr
底层格式 HDF5 列式二进制 分块数组
部分读取 支持(按变量) 支持(按列/行组) 支持(按块)
压缩 zlib Snappy/GZIP/ZSTD Blosc/ZSTD
MATLAB 原生支持 是 需 Parquet 工具箱 需第三方包
适用场景 MATLAB 生态内交换 数据分析、列式查询 云原生、大规模数组

本文评述:选择持久化格式的本质,是选择“边界画在哪里”。MAT 文件的边界画在 MATLAB 生态内,优点是原生支持、类型完整;Parquet 的边界画在列式分析生态内,优点是查询效率高、跨语言好;Zarr 的边界画在云原生数组生态内,优点是分块灵活、适合分布式。没有绝对优劣,只有场景匹配。

7.3 数据库与对象存储的集成

在工程化数据管道中,MAT 文件往往不是终点,而是中间环节。典型架构是:MATLAB 产生数据 → 保存为 MAT → 由 Python/Spark 读取 → 写入数据库或对象存储。在这种架构下,MAT 文件扮演的是“交换格式”角色,而非“存储格式”。

对于需要长期存储的数据,建议在 MAT 文件之外,同时保存一份 Parquet 或 Zarr 副本,并记录数据转换的元数据(如维度顺序、单位、预处理步骤)。这样即使未来 MATLAB 版本升级导致 MAT 格式变化,数据仍然可读。

第八章 版本兼容、安全与可复现性

8.1 向前兼容与向后兼容

MAT 文件格式在版本间保持了较好的向后兼容性:新版本 MATLAB 可以读取旧版本 MAT 文件。但向前兼容性有限:旧版本 MATLAB 无法读取新版本格式。例如,MATLAB R2020a 无法读取 v7.3 格式的文件,除非安装了 HDF5 支持。

工程上的建议是:如果数据需要在不同 MATLAB 版本间共享,保存时显式指定一个较低的版本,如 save(..., '-v7')。如果数据只在当前版本使用,则可以用默认格式。

8.2 安全考量:加载不可信 MAT 文件的 risks

MAT 文件可以包含可执行代码吗?答案是:在特定条件下可以。如果 MAT 文件中保存了对象,加载时 MATLAB 会调用该对象的 loadobj 方法。如果攻击者构造了一个恶意 MAT 文件,其中包含一个类的实例,而该类的 loadobj 方法包含恶意代码,那么加载该文件时就会执行恶意代码。

笔者认为,这是一个被严重低估的安全风险。在协作环境中,从不可信来源获取 MAT 文件时,应该:

  • 使用 whos('-file', filename) 先查看文件内容,确认没有可疑的类实例
  • 在沙箱环境中加载,或使用 load 的结构体形式配合字段白名单
  • 对于 v7.3 文件,可以用 h5py 等工具先检查 HDF5 结构,再决定是否用 MATLAB 加载

8.3 可复现性:记录数据来源与预处理

在科研和工程中,数据可复现性至关重要。一个 MAT 文件如果只包含数值,而不包含数据来源、预处理步骤、单位等信息,那么几个月后连作者自己都可能无法理解。

建议在保存数据时,同时保存一个元数据变量:

% 数据本体
signal = acquireSignal();
fs = 1000;

% 元数据
metadata = struct();
metadata.source = 'sensor_A';
metadata.acquisitionDate = datetime('now');
metadata.sampleRate = fs;
metadata.units = 'mV';
metadata.preprocessing = {'detrend', 'bandpass 0.5-40 Hz'};
metadata.matlabVersion = version;
metadata.description = 'Resting-state EEG, subject 01';

% 一起保存
save('experiment_01.mat', 'signal', 'fs', 'metadata', '-v7.3');

这种做法虽然增加了一点存储开销,但极大提升了数据的可理解性和可复现性。在团队协作中,元数据规范应该作为数据管理流程的一部分被强制执行。

第九章 前沿研判与选型决策框架

9.1 数据持久化的前沿趋势

近三年,数据持久化领域出现了几个值得关注的趋势:

🔒 复制本站文章内容需登录并达到 L3。当前:未登录

分享到

💬
微信
📷
朋友圈
🐧
QQ好友
🌐
QQ空间
👁
微博
📌
钉钉
🔗
复制链接
📑
复制图文

微信扫一扫分享

打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。

💬 评论 (0)

评论功能已关闭

⏸️ 本站暂未开放评论功能,不能进行评论,此为规划的后续开发预留
首页| 关于本网| 网站声明| 联系我们| 网站纠错| 服务| 网站地图
黔ICP备19010680号-1  |  邮箱:six528528@163.com
贵公网安备 52010302001819号
Copyright 2019-2026 http://www.databrush.com/ All rights reserved.
QQ
QQ扫一扫
Logo
DBN数据刷