从 save/load 的底层字节布局,到 -v7.3 HDF5、跨语言互操作与云端持久化的工程实践
技术深度 · 工程路径 · 前沿预判
摘要
工作区持久化看似只是点一下“Save Workspace As”,但它牵涉到数据序列化格式、内存布局、跨版本兼容、跨语言互操作以及大规模数据工程等一系列问题。本文以 MATLAB 的 save / load 为主线,逐层拆解 .mat 文件的版本演进(v4、v6、v7、v7.3)、HDF5 容器结构、变量序列化规则、结构体与元胞数组的嵌套存储、稀疏矩阵与对象句柄的处理方式,并进一步延伸到分块存储、内存映射、云端对象存储、Python/Julia/R 互操作等工程场景。全文贯穿一条主线:持久化不是“存下来”这么简单,而是“以可被未来工具正确还原的方式存下来”。围绕这条主线,本文给出可复现的操作步骤、参数对照表、性能取舍建议,并对 MAT 格式在异构计算时代的位置做出独立判断。
目录
1. 为什么“存盘”是一个被低估的工程问题
在 MATLAB 的日常使用中,save 和 load 几乎是所有教程里最先出现的两条命令。它们简单到让人觉得不需要解释:一条把变量写进 .mat 文件,一条把它读回来。但真正在工程环境里跑过几年项目的人,几乎都踩过这样的坑:换了一台机器,load 报版本不兼容;文件明明只有 2 GB,却提示“变量过大无法保存”;用 Python 读 .mat 时结构体字段全变成了奇怪的对象;把文件传到云端再拉回来,校验和变了。
这些问题的根源,是很多人把“持久化”理解成了“把内存里的东西倒到磁盘上”。而真正意义上的持久化,是让数据在未来某个时间点、被某个可能不同的工具、以正确的语义还原出来。这中间至少涉及四层问题:格式层(字节怎么排)、语义层(类型怎么表达)、工具层(谁来读)、治理层(版本怎么管)。save 和 load 只是这四层里最上面的一层薄壳。
本文评述:笔者认为,把 .mat 文件类比成“MATLAB 的私有数据库”比类比成“文本文件”更贴切。它有自己的 schema(变量名、类型、维度)、有自己的存储引擎(v4 的流式布局 vs v7.3 的 HDF5)、有自己的版本迁移问题。一旦用数据库的视角去看待它,很多“玄学问题”就变成了可分析、可规避的工程问题。
从数据量角度看,这种视角转变的必要性越来越强。根据 MathWorks 官方文档与社区统计(MathWorks Documentation, 2024),MATLAB 用户中处理超过 1 GB 工作区数据的比例在过去五年显著上升,而 v7 格式对单个变量的 2 GB 硬上限,正是大量“保存失败”报错的直接原因。这不是一个可以靠“多按几次 Ctrl+S”解决的问题,它需要理解格式本身。
1.1 一条贯穿全文的主线
本文的主线可以概括为一句话:持久化的本质是“契约”,而不是“拷贝”。你写下的每一个 .mat 文件,都是对未来的自己(或同事、或另一个程序)的一份契约:我承诺这些字节能被还原成这些变量。契约能不能兑现,取决于格式版本、类型表达、工具支持三者是否对齐。后续每一节,都会回到这条主线上来。
2. .mat 文件格式的版本谱系与底层结构
要理解 save 的行为,必须先知道它写出来的是什么。.mat 并不是一种单一格式,而是一族格式的统称。不同版本在字节布局、压缩方式、支持的数据类型上差异很大。
2.1 版本谱系
这张表里最值得注意的一行是 -v7.3。它不是一个“更好的 v7”,而是一次容器层面的切换:从 MathWorks 自研的二进制流,换成了 HDF5。这个切换带来的连锁反应,是本文后半部分很多讨论的起点。根据 HDF Group 的技术文档(The HDF Group, 2023),HDF5 采用分层结构:文件是根组,组下面可以挂数据集(dataset)和属性(attribute),数据集支持分块(chunking)、压缩、扩展维度。MATLAB 把每个变量映射成根组下的一个数据集,把变量名、类型等元信息放进属性里。
本文评述:笔者认为,-v7.3 的真正价值不在于“能存更大”,而在于“能被别人读”。HDF5 是一个有独立规范、有 C/Fortran/Python/Java 多语言绑定的开放格式。一旦数据落在 HDF5 里,它就脱离了 MATLAB 的私有生态,进入了科学计算的公共生态。这是从“私有格式”到“公共契约”的一次跃迁,代价是文件结构更复杂、小文件开销更大。
2.2 一个最小 .mat 文件的字节解剖
以 v7 格式为例,文件开头是 128 字节的头部。前 116 字节是描述性文本(通常包含创建平台、时间等信息),随后是 8 字节的子系统数据偏移量,再是 2 字节的版本号,最后 2 字节的“endian indicator”用来标识字节序(IM 表示小端,MI 表示大端)。这个设计让读取方在解析任何数据之前,就能判断自己能不能读、以什么字节序读。
% 查看 .mat 文件头部信息(示意)
fid = fopen('data.mat','r');
header = fread(fid, 128, '*uint8');
fclose(fid);
char(header(1:16))' % 描述性文本片段
header(125:126)' % 版本号
char(header(127:128)) % 字节序标识
128 字节之后是数据元素(data element)的连续排列。每个元素有 8 字节的 tag:4 字节数据类型 + 4 字节字节数,后面跟数据本体。这种“tag-length-value”式的布局,是很多二进制格式的经典做法。它的优点是解析简单、顺序读取快;缺点是随机访问需要从头扫描,且不支持原地扩展。
本文评述:笔者认为,v7 的流式布局和 v7.3 的 HDF5 布局,代表了两种截然不同的设计哲学。前者优化“顺序写、顺序读”的批处理场景,后者优化“按需读、部分读、跨工具读”的交互场景。选择哪个版本,本质上是在选择你的数据未来会被怎样使用。如果数据只是自己当天用完就删,v7 足够;如果数据要进数据湖、要被多个团队读、要保留数年,v7.3 是更负责任的选择。
3. save 的参数体系:从 -v7.3 到 -append 的完整拆解
MATLAB 的 save 命令参数很多,但真正影响工程决策的其实就那么几个。这一节把常用参数按“影响格式”“影响内容”“影响行为”三类拆开讲。
3.1 影响格式的参数
-v7.3 是最常用的一个。它的触发条件往往是“文件超过 2 GB”或“需要 Python 读”。但要注意,-v7.3 写出的文件在旧版 MATLAB(R2006b 之前)里无法读取,这在需要向下兼容的团队里是个硬约束。-v6 和 -v4 现在主要用于对接老旧系统或特定硬件工具链。
-nocompression 值得单独说。v7 默认开启 zlib 压缩,这对文本型、稀疏型数据压缩率很高,但对已经压缩过的数据(如 JPEG 图像、某些浮点噪声)几乎无效,反而增加 CPU 开销。在需要极致写入速度的场景(如实时采集落盘),关掉压缩可能让吞吐提升明显。具体提升幅度取决于数据可压缩性,建议用 tic/toc 实测。
3.2 影响内容的参数
-append 是个容易踩坑的参数。它在 v7 下可以往已有文件里加变量,但如果新变量与已有变量同名,会直接覆盖,且不给出警告。更麻烦的是,-append 与 -v7.3 不能同时使用。在需要增量写入的场景,更稳妥的做法是用 HDF5 的底层接口(h5write)直接操作数据集,而不是依赖 save -append。
本文评述:笔者认为,-append 的设计反映了早期 MATLAB 对“文件即工作区快照”的定位,而不是“文件即数据库”。一旦你把 .mat 当成需要频繁更新的数据容器,就应该转向 HDF5 接口或干脆换用其他存储方案。用 save -append 做增量更新,长期看是技术债。
3.3 一个可复用的保存函数模板
function saveWorkspaceSafe(outPath, varargin)
% 安全保存工作区:自动选择格式、记录元信息、校验写入
p = inputParser;
addParameter(p, 'Include', {}); % 要保存的变量名
addParameter(p, 'Exclude', {}); % 要排除的变量名
addParameter(p, 'Force73', false); % 强制 v7.3
parse(p, varargin{:});
opts = p.Results;
vars = evalin('base', 'who');
if ~isempty(opts.Include)
vars = intersect(vars, opts.Include);
end
if ~isempty(opts.Exclude)
vars = setdiff(vars, opts.Exclude);
end
% 估算总大小,决定格式
info = whos('-regexp', strjoin(vars, '|'));
totalBytes = sum([info.bytes]);
use73 = opts.Force73 || totalBytes > 1.5e9;
if use73
save(outPath, vars{:}, '-v7.3');
else
save(outPath, vars{:}, '-v7');
end
% 写入后校验
d = dir(outPath);
fprintf('Saved %d vars, %.2f MB, format=%s\n', ...
numel(vars), d.bytes/1e6, ternary(use73,'v7.3','v7'));
end
function r = ternary(c, a, b)
if c, r = a; else, r = b; end
end
这个模板做了三件工程上很实用的事:一是根据数据量自动选格式,避免手动判断;二是支持白名单/黑名单,避免把临时变量也存进去;三是保存后回读文件大小并打印,便于日志追踪。它不是“最优”实现,但比裸调 save 更接近生产环境的要求。
4. load 的还原语义:作用域、覆盖与懒加载
load 看起来比 save 简单,但它有几个容易忽略的语义细节,直接影响代码的正确性。
4.1 输出形式决定作用域
load('data.mat') 不带输出参数时,会把变量直接注入当前工作区。如果当前工作区已有同名变量,会被静默覆盖。这在函数内部尤其危险:一个函数里调用 load,可能无意中覆盖了函数的局部变量,而且不报错。
S = load('data.mat') 带输出参数时,返回一个结构体,字段名就是变量名。这种方式不会污染工作区,是函数内部读取数据的推荐写法。代价是多一层结构体访问,以及对于超大变量,结构体包装本身有轻微内存开销。
% 推荐:函数内部用结构体形式,避免污染
function result = analyze(filePath)
S = load(filePath);
if ~isfield(S, 'signal')
error('缺少 signal 变量');
end
result = process(S.signal);
end
% 不推荐:直接 load 进函数工作区
function result = analyzeBad(filePath)
load(filePath); % signal 变量凭空出现,可读性差
result = process(signal);
end
4.2 选择性加载与懒加载
当 .mat 文件很大时,全量加载既慢又占内存。v7.3 格式支持按变量选择性加载:load('big.mat', 'varA', 'varB') 只读指定变量。在 HDF5 层面,MATLAB 会只读取对应的数据集,而不是扫描整个文件。这是 v7.3 相对 v7 的一个实质性优势。
更进一步的懒加载思路,是用 matfile 函数。它返回一个类似文件句柄的对象,支持按索引读取数组的一部分,而不需要把整个变量读进内存。
% matfile 懒加载示例
m = matfile('largeData.mat'); % 不立即读数据
sz = size(m.bigMatrix); % 只读元信息
chunk = m.bigMatrix(1:1000, :); % 只读前 1000 行
m.bigMatrix(5001:6000, :) = newData; % 支持部分写入(v7.3)
本文评述:笔者认为,matfile 是 MATLAB 里被严重低估的一个工具。它把 .mat 文件从“整存整取”的容器,变成了“可随机访问”的数据源。对于超出内存的数据集,这是比“分块存多个文件”更优雅的方案。但它只支持 v7.3 格式,且部分写入需要文件系统支持,在 NFS 等网络文件系统上行为可能不稳定,需要实测。
4.3 版本兼容的还原边界
一个常被问到的问题是:新版 MATLAB 存的 .mat,旧版能读吗?答案取决于格式和数据类型。v7 文件在 R2006b 之后的版本基本都能读;v7.3 需要 R2006b 之后且开启了 HDF5 支持的版本。但即使格式能读,某些新数据类型(如 string 类型、table、datetime)在旧版里可能被还原成结构体或直接报错。这是一个语义层的兼容问题,不是字节层的问题。
工程上的应对策略是:如果数据需要长期归档且可能被旧工具读取,保存时主动降级为“基础类型”——把 string 转成 char,把 table 转成结构体数组。这会在当下损失一些便利,但换来的是更长的可读寿命。
5. 复杂数据类型的序列化:结构体、元胞、稀疏与对象
基础数值数组的序列化很直接:维度信息加连续内存块。但 MATLAB 的数据类型远不止数值数组,结构体、元胞数组、稀疏矩阵、函数句柄、面向对象实例,每一种都有自己的序列化规则和坑。
5.1 结构体与元胞数组
结构体在 .mat 里被存成“字段名数组 + 字段值数组”的形式。如果结构体数组的各个元素字段不一致(MATLAB 允许这种情况,虽然不推荐),保存时会被拆成多个字段组,读取时可能得到意料之外的结构。元胞数组则被存成“每个元素的独立记录”,因此元胞数组的读取开销通常比同尺寸数值数组大。
一个实用建议是:在保存前用 struct2table 把规整的结构体数组转成 table,或者用 cell2mat 把同构元胞转成数值数组。这能显著减小文件体积、加快读写。
5.2 稀疏矩阵的存储优势
稀疏矩阵是 .mat 格式里少数“专门优化过”的类型。它只存非零元素的行索引、列索引和值,加上维度信息。对于一个 10000×10000、非零率 0.1% 的矩阵,稠密存储需要约 800 MB(双精度),稀疏存储只需要约 1.6 MB 量级。这个差距在保存和加载时都能明显感受到。
本文评述:笔者认为,稀疏矩阵的序列化是“格式感知数据”的一个好例子。同一个数学对象,用不同的存储策略,代价可以差几个数量级。这提醒我们:在 save 之前,先问一句“我的数据真的需要稠密存储吗”,往往比调任何参数都有效。
5.3 对象与函数句柄:能存什么,不能存什么
MATLAB 的面向对象实例可以保存,但保存的是“属性值的快照”,不是对象的行为。类定义本身不在 .mat 文件里。如果读取时类定义不存在或已改变,MATLAB 会尝试用当前类定义还原,可能失败或产生不一致的对象。函数句柄的保存更微妙:匿名函数句柄保存的是其捕获的工作区变量,如果捕获了很大的数据,文件会意外变大。
% 匿名函数句柄可能隐式捕获大变量
bigData = rand(1e7, 1);
f = @(x) x + mean(bigData); % f 捕获了 bigData
save('func.mat', 'f'); % 文件会包含 bigData 的副本
% 更可控的写法:显式传参,避免隐式捕获
g = @(x, mu) x + mu;
mu = mean(bigData);
save('func2.mat', 'g', 'mu');
工程上的原则是:保存数据,而不是保存计算逻辑。函数句柄、对象实例这类“带行为”的东西,尽量在读取后重新构造,而不是依赖 .mat 里的快照。这样代码的可维护性和可移植性都会更好。
6. 大文件工程:分块、压缩、内存映射与并行 I/O
当工作区数据从 MB 级进入 GB 甚至 TB 级,save / load 的默认行为就不再适用。这一节讨论几种工程上常用的应对策略。
6.1 分块存储的设计
把一个大数组拆成多个 .mat 文件,是最朴素也最可靠的做法。关键决策是“按什么维度切”。对于时间序列,按时间窗口切;对于图像集合,按样本切;对于矩阵,按行或列块切。切分后要维护一个索引文件,记录每块的变量名、维度范围、文件路径。
% 分块保存大矩阵
function chunkedSave(data, outDir, chunkRows)
if ~exist(outDir, 'dir'), mkdir(outDir); end
[nRows, nCols] = size(data);
idx = struct('file', {}, 'rowRange', {}, 'colRange', {});
k = 0;
for r = 1:chunkRows:nRows
k = k + 1;
rEnd = min(r + chunkRows - 1, nRows);
fname = fullfile(outDir, sprintf('chunk_%04d.mat', k));
block = data(r:rEnd, :);
save(fname, 'block', '-v7.3');
idx(k).file = fname;
idx(k).rowRange = [r, rEnd];
idx(k).colRange = [1, nCols];
end
save(fullfile(outDir, 'index.mat'), 'idx');
end
这种方案的优点是实现简单、可并行、单块失败不影响全局。缺点是需要自己管理索引,且跨块操作(如按列聚合)需要额外逻辑。本文评述:笔者认为,分块存储的价值不仅在于绕开大小限制,更在于它把“一个大任务”拆成了“多个可独立处理的小任务”,天然适合并行和断点续算。在数据工程里,这往往比单纯追求“单文件更大”更有价值。
6.2 压缩的取舍
v7 的 zlib 压缩和 v7.3 的 HDF5 压缩,都是“时间换空间”。压缩率取决于数据的熵:浮点噪声几乎不可压缩,整数索引、稀疏结构、重复模式压缩率很高。在写入密集的场景,压缩可能成为瓶颈。一个实用的判断方法是:先用 save 存一次带压缩、一次不带压缩,比较文件大小和耗时,再决定。
上表压缩率为模拟数据,用于说明趋势,实际值需以具体数据实测为准。来源:笔者基于 zlib 压缩特性的整合估算。
6.3 并行 I/O 与内存映射
在有多核、多节点的集群上,可以用 parfor 并行保存多个分块文件。但要注意,多个 worker 同时写同一个 .mat 文件是不安全的,必须每个 worker 写自己的文件。内存映射(memory-mapped file)是另一条路:用 memmapfile 把文件映射成内存数组,读写像操作普通数组一样。它适合“固定格式、频繁随机访问”的场景,但不适合变长记录。
本文评述:笔者认为,大文件工程的本质矛盾是“内存有限、数据无限”。所有策略——分块、压缩、懒加载、内存映射——都是在不同维度上缓解这个矛盾。没有银弹,只有权衡。选择哪种,取决于你的访问模式:是顺序扫描多,还是随机读取多;是写一次读多次,还是频繁更新。先搞清楚访问模式,再选工具,顺序不能反。
7. 跨语言互操作:Python、Julia、R 与 HDF5 生态
现代科研和工程很少只用一种语言。MATLAB 存的数据,经常需要被 Python 脚本、Julia 计算内核或 R 统计包读取。.mat 的跨语言能力,直接决定了它在多语言流水线里的位置。
7.1 Python 读 .mat:scipy.io 与 h5py
Python 生态里读 .mat 有两条主流路径。一条是 scipy.io.loadmat,它支持 v4、v6、v7,但对 v7.3 无能为力。另一条是 h5py,它把 v7.3 文件当普通 HDF5 读,能处理大文件,但需要自己理解 MATLAB 的数据布局约定。
# 路径一:scipy.io(v7 及以下)
from scipy.io import loadmat
data = loadmat('data_v7.mat')
signal = data['signal'] # numpy 数组
# 路径二:h5py(v7.3)
import h5py
import numpy as np
with h5py.File('data_v73.mat', 'r') as f:
signal = np.array(f['signal']).T # 注意转置!
# MATLAB 列主序,HDF5 行主序,读出来需要转置
这里有一个非常关键的细节:MATLAB 数组是列主序(column-major),HDF5 和 NumPy 默认是行主序(row-major)。用 h5py 读出来的数组,维度顺序和 MATLAB 里看到的相反,需要转置。这是跨语言读 .mat 最常见的 bug 来源之一。
本文评述:笔者认为,列主序/行主序的差异,是“格式契约”里最容易被忽视的一条。它不会报错,只会让结果悄悄错掉。工程上的应对是:在跨语言接口的文档里明确写出维度约定,并在单元测试里用非对称矩阵(如 3×5)验证转置是否正确。用方阵测试是发现不了这个问题的。
7.2 Julia 与 R 的读取方案
Julia 生态里,MAT.jl 提供了较完整的 .mat 读写支持,包括 v7.3。R 生态里,R.matlab 包可以读写 v5/v6/v7 格式,v7.3 支持有限,通常建议用 rhdf5 直接读 HDF5。这些工具的存在,让 .mat 在统计计算和数值计算社区之间有了桥梁。
7.3 互操作的最佳实践
- 如果数据要被 Python 读,优先用
-v7.3,并接受 h5py 的转置约定。 - 避免在
.mat里存 MATLAB 特有的复杂类型(table、categorical、datetime),跨语言读取时容易出问题。 - 变量名用 ASCII,避免中文或特殊字符,减少编码问题。
- 在文件里附一个 README 变量,用字符串说明各变量的含义、维度、单位。
8. 云端与版本化:对象存储、DVC 与可复现研究
数据一旦离开本地磁盘,进入云端对象存储或版本控制系统,.mat 文件的治理问题就浮现出来。
8.1 对象存储的适配
S3、OSS、GCS 这类对象存储,适合存“写一次、读多次”的大文件。它们不支持文件内的随机写入,所以 matfile 的部分写入功能在对象存储上无法直接使用。工程上的做法是:本地生成完整 .mat,再整体上传;或者用分块文件,每块独立上传。
上传后要记录校验和(如 MD5、SHA-256),因为网络传输可能引入损坏。MATLAB 里可以用 Simulink.getFileChecksum 或调用系统命令计算。下载后比对校验和,是保证数据完整性的基本操作。
8.2 数据版本控制
Git 不适合直接管理大的 .mat 文件,因为二进制文件无法有效 diff,且会让仓库膨胀。DVC(Data Version Control)是常用的替代方案:它把大文件存在远端(S3、NAS 等),Git 里只存一个指向文件的元数据指针。这样既能版本化数据,又不污染代码仓库。
# DVC 基本流程 dvc init dvc add data/experiment_01.mat git add data/experiment_01.mat.dvc .gitignore git commit -m "add experiment 01 data" dvc push # 上传到远端存储 # 其他人拉取 git pull dvc pull # 从远端下载 .mat 文件
本文评述:笔者认为,DVC 这类工具解决的不只是“文件太大”的问题,更是“数据与代码如何对应”的问题。在可复现研究里,一个常见的失败模式是:代码更新了,但数据还是旧的,结果对不上。把数据版本和代码版本绑定,是避免这类问题的有效手段。.mat 文件作为数据载体,天然适合纳入这种版本化管理。
8.3 可复现性的元数据设计
一个负责任的 .mat 文件,应该自带足够的元信息,让未来的人能理解它。建议在保存时额外写入一个 meta 结构体,包含:创建时间、MATLAB 版本、数据来源、预处理步骤、变量说明、单位、联系人。这些信息占不了多少空间,但能极大降低后续使用的沟通成本。
meta = struct();
meta.createdAt = datestr(now, 'yyyy-mm-dd HH:MM:SS');
meta.matlabVersion = version;
meta.source = 'sensor_array_A, 2024-03 campaign';
meta.preprocessing = 'bandpass 0.5-40 Hz, notch 50 Hz';
meta.variables = {'signal: [nSamples x nChannels], uV', ...
'fs: scalar, Hz', ...
'channelNames: 1 x nChannels cell'};
meta.contact = 'lab@example.org';
save('experiment.mat', 'signal', 'fs', 'channelNames', 'meta', '-v7.3');
9. 前沿预判:MAT 格式在异构计算时代的位置
讨论完工程实践,有必要抬头看看趋势。.mat 格式诞生于单机、单语言、以数值数组为中心的时代。今天的环境已经大不相同:GPU 计算、分布式集群、多语言流水线、云原生部署。这些变化会如何影响 .mat 的角色?
9.1 与列式格式的竞争
Parquet、Arrow、Zarr 这些为大数据和异构计算设计的格式,正在科学数据领域快速普及。Parquet 适合表格型数据,Arrow 适合内存中的列式表示,Zarr 适合分块的 N 维数组。它们大多有成熟的 Python/R/Julia 支持,且与云存储、并行计算框架集成良好。相比之下,.mat 的优势在于与 MATLAB 生态的深度绑定,以及“一个文件装下整个工作区”的便利。
本文评述:笔者认为,.mat 不会消失,但它的定位会从“通用数据交换格式”收缩为“MATLAB 工作区快照格式”。跨语言、跨团队的数据交换,会越来越多地走 Parquet/Arrow/Zarr 这条路。.mat 的 v7.3 选择 HDF5 作为底层,某种程度上是在为这个收缩做准备——至少数据还能被其他工具读出来。
9.2 GPU 与分布式数据的持久化
当数据常驻 GPU 显存时,save 需要先把数据拷回主机内存,再写磁盘。这个往返在大数据量下可能成为瓶颈。一些工具箱(如 Parallel Computing Toolbox)提供了 gather 后再保存的路径,但本质上没有改变“显存→内存→磁盘”的链路。分布式数组(distributed array)的保存,则需要各 worker 协同写出分块文件。
一个值得关注的方向是“计算靠近数据”:与其把数据从存储拉到计算节点,不如把计算调度到数据所在的节点。这对 .mat 文件意味着,未来的持久化方案可能需要支持“在存储端直接执行部分计算”,而不只是简单的读写。这已经超出了当前 save/load 的范畴,但值得持续观察。
9.3 长期归档的格式选择
对于需要保存十年以上的科研数据,格式的长期可读性比读写性能更重要。HDF5 有独立的非营利组织维护规范,有 C 库的长期支持,这比私有二进制格式更让人放心。-v7.3 在这方面比 -v7 更有优势。但即使选了 HDF5,也要注意:MATLAB 在 HDF5 上加了特定的属性约定,这些约定本身没有公开的正式规范,未来版本可能变化。最稳妥的长期归档,是把数据同时导出为“纯 HDF5 + 一份说明文档”,减少对 MATLAB 特定约定的依赖。
10. 工程实践清单与常见陷阱
把前面各节的要点浓缩成一份可执行的清单,方便在实际项目中对照使用。
10.1 保存前检查清单
- 用
whos确认要保存的变量,排除临时变量(如循环计数器、绘图句柄)。 - 估算总大小,超过 1.5 GB 时考虑
-v7.3。 - 检查是否有匿名函数句柄捕获了大变量。
- 检查是否有 MATLAB 特有类型需要跨语言读取。
- 决定是否写入
meta元信息结构体。 - 确认输出路径存在且有写权限。

