ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Matlab读取Fluent瞬态结果:高效后处理与实战代码解析

2026/9/9 13:28:30 拓冰建站 浏览量
Matlab读取Fluent瞬态结果:高效后处理与实战代码解析 上一期写了一篇用Matlab处理Fluent瞬态结果的基础流程评论区不少朋友留言说已经能把数据读进来但真正落到自己项目里还是有不少边边角角的问题比如时间步对不上、文件太大读不动、云图画出来乱糟糟的。这篇文章接着往下走重点聊两件事一是怎么把Matlab读取Fluent瞬态结果这件事做得更稳、更快、更省内存二是读进来之后到底能做哪些像样的后处理分析。文章适合两类人看一类是已经能跑通基础流程、但想提升效率和稳定性的仿真工程师另一类是刚开始接触瞬态仿真数据、被一大堆时间步文件搞得焦头烂额的新手。我会把关键代码、参数选择逻辑和踩坑记录都放出来尽量做到可以照着抄。1. 先想清楚后处理要什么再决定怎么读数据1.1 从“看一张图”到“理解一段过程”瞬态结果后处理的真实需求很多人拿到Fluent瞬态计算结果后第一反应是“我把每个时间步的云图导出来用Matlab画一遍不就行了”。这个思路本身没错但它离真正的后处理需求还差得很远。我这两年帮几个团队做过流体数据整理发现大家真正想从瞬态结果里拿到的通常不外乎这几类动态演示把几十个甚至几百个时间步连成动画直观看到流场随时间的变化比如涡的脱落、温度前锋的推进、压力波的传播。监测点时间序列在某个特定位置比如壁面热流最大点、流道中心的某个节点提取物理量随时间的变化曲线用于和实验数据对比。统计特征计算时均场、脉动场、湍流统计量这是做周期性流动或湍流分析的基础。频谱分析对某个监测点的时间序列做FFT找出主频比如圆柱绕流的涡脱落频率。这几类需求对数据读取的要求是完全不一样的。如果你只是做动画那每个时间步的数据都可以用一次读一次用完就扔但如果你要做时均场或者频谱就必须把整个时间序列的数据都留在内存里或者分批累加统计量。所以开始写代码之前先明确后处理的最终目标这个非常重要直接决定了你的数据组织方式和内存管理策略。1.2 Fluent导出的数据到底长什么样格式对比与取舍Fluent瞬态结果导出的方式非常灵活常见的无非几种ASCII文本.dat/.csv、Ensight Gold格式、CGNS格式、以及新版Fluent支持的HDF5格式。这里我不评价哪种“最好”因为不同场景下最适合的真的不一样我只说我的实际体会格式优点缺点适合场景ASCII文本每时间步一个文件结构直观、可读性强、Matlab轻松解析文件体积大、读起来慢时间步数不多少于几百、需要人工检查数据ASCII文本单文件多时间步一个文件搞定文件极大、定位时间步要额外处理时间步较少且单步数据量不大的情况Ensight GoldFluent原生支持好、可记录网格多时间步二进制布局复杂Matlab解析需要自己写底层函数企业级项目数据量很大HDF5读写快、支持并行、自带压缩需要一点HDF5知识Fluent导出配置稍复杂时间步多、单个网格量大、需要反复读取我自己最常用的做法是如果时间步少于300步直接用每步一个ASCII文件如果时间步上千或者网格有几百万单元那就老老实实导出HDF5或者调用Fluent的TUI/Journal脚本直接把数据写入为逐时间步的文本。原因很简单Matlab读文本的textscan虽然足够稳定但上百万个单元的坐标加变量每个文件动辄几百MB文本解析会慢到让人怀疑人生。2. Matlab读取前的关键准备把Fluent的“结果”变成Matlab的“数据”2.1 导出瞬态结果时要注意的三个坑先说三个我在实际项目中踩过、后来才发现源头在导出阶段的坑这些坑会直接影响Matlab读取效率和数据正确性越早规避越好。第一个坑导出变量种类太多。很多人做后处理其实只关心速度和压力但导出的时候图省事直接把所有变量全选了一并导出。结果就是文件体积暴涨Matlab读取时间飙升而绝大多数数据你根本用不上。建议导出时只勾选当前分析真正需要的变量比如速度分量、静压、温度、湍动能。如果后面发现需要别的变量重新导出也不麻烦至少比每次都扛一个几GB的文本文件舒服得多。第二个坑时间步的命名和顺序混乱。Fluent在批量导出时默认的文件名通常像“export_00001.dat”“export_00002.dat”这种带有序号的。但如果你用某些脚本批量重命名过或者中间删除过某些时间步那顺序就乱了。Matlab批量读取时最怕这个因为它默认按文件名排序一旦顺序错乱后面的时间序列分析全部白做。建议在Fluent导出时就用统一的命名规则或者在Matlab里用自然排序对数字部分排序而不是纯字符串排序来读取文件列表。第三个坑单位制不一致。Fluent模型里几何如果是按毫米建的导出的坐标单位就是毫米而你后处理时想用的是米或者压力是一个相对值而你想要绝对值。这种问题在Matlab里修正不难难的是你忘记有这回事。我建议在读取代码里写死一个单位换算因子并且在注释里标明“几何尺寸缩放mm-m压力增加101325 Pa”避免过一周自己回来看代码时一脸茫然。2.2 数据文件的组织结构读懂Fluent导出的文本要想写出稳定的读取代码必须先搞清楚Fluent导出的ASCII文件内部结构。不同版本的Fluent、不同导出方式格式会略有差异但大体上遵循一套相似的套路。以最常见的“导出为ASCII列出坐标和单元连接”为例文件里通常分成三块头信息段文件开头是一段以括号包裹的文本里面写着变量名、节点数、单元数、时间值等信息。例如“(2 0 0 0)”或“(10 (0 1 0 0))”这种带有数字标识的段落具体含义跟Fluent内部的数据结构版本有关。网格段列出所有节点的三维坐标以及单元连接关系即单元由哪几个节点组成。这个段落的数据量最大也是最容易让新手抓狂的部分。数据段按变量一一列出每个节点的数值格式通常是每个节点一个值按节点编号顺序排列。如果同时导出多个变量前面会有一段说明性文字标注变量名称。在我常用的导出方式里每步一个文本文件网格段在每个时间步的文件里都会重复出现这其实非常浪费空间。所以我后来干脆把头几步的网格信息单独读出来后续时间步只读数据段这样能省不少时间。如果你导出之前有心甚至可以在Fluent里设置成“不保存坐标”只输出变量值然后在Matlab里复用第一次读到的网格信息效果更好。2.3 在Matlab里搭建可复用的读取框架刚接触这个流程的人很容易把每个脚本都写成从头到尾的线性流程打开文件、读网格、读数据、画图。这种做法如果只是做一次性的后处理没什么问题但如果你要处理十几个case、每个case几百个时间步那就痛苦了。我的建议是搭一个简单的读取框架分为三层第一层底层IO函数负责把Fluent导出的一个文本文件解析成Matlab数据结构坐标矩阵、单元连接矩阵、变量数据矩阵。第二层批量读取函数负责遍历目录下的所有时间步文件依次调用底层IO函数把数据组装成一个结构体数组一个元素对应一个时间步。第三层后处理函数读取这个结构体数组完成动画、统计、频谱等分析输出最终图像或数据文件。这个框架的好处是一旦某个环节出问题你能快速定位到是读取问题、批量组装问题还是后处理问题而且后续如果换了其他格式比如HDF5只需要替换底层IO函数上层后处理代码完全不用动。3. 实战Matlab读取Fluent瞬态结果的核心代码3.1 读取ASCII导出文件的完整流程假设Fluent导出的每个时间步是一个文本文件文件内容的大致格式如下简化版TITLE Fluent ASCII Export VARIABLES X Y Z V_x V_y V_z P ZONE TTIME0.100000, N1000, E5000, DATAPACKINGPOINT, ZONETYPEFETETRAHEDRON -0.01 0.02 0.0 1.23e-3 -0.45e-3 0.0 101325.0 ...这里的每一行就是一条数据前三个是坐标后面是各个变量值空格分隔。这个格式和Tecplot导出的格式很像。如果变量多甚至可能用逗号分隔。一个稳定好用的读取函数可以这样写function [nodePos, elemConn, varData] readFluentASCII(fileName, nColsExpected) % 读取 Fluent 导出的单时间步 ASCII 结果 % 输入 % fileName - 文件名 % nColsExpected - 期望的列数坐标 变量数 % 输出 % nodePos - Nx3 节点坐标 % elemConn - Mx4 四面体单元连接按节点编号 % varData - Nxv 变量数据 fid fopen(fileName, r); if fid -1 error(无法打开文件: %s, fileName); end % 跳过所有以 TITLE / VARIABLES / ZONE 开头的行 dataStartFound false; while ~dataStartFound tline fgetl(fid); if ~ischar(tline) error(文件格式非法未找到数据起始位置); end if startsWith(strtrim(tline), TITLE) ... || startsWith(strtrim(tline), VARIABLES) ... || startsWith(strtrim(tline), ZONE) ... || startsWith(strtrim(tline), DATASETAUXDATA) continue; else dataStartFound true; % 此时 tline 已经是第一行数据 end end % 把第一行数据和后续所有行一起读入 allData textscan(fid, repmat(%f, 1, nColsExpected), ... Delimiter, , MultipleDelimsAsOne, true, ... HeaderLines, 0, EmptyValue, NaN); fclose(fid); % 把 tline 转为数值并合并 firstRow sscanf(tline, %f); allDataMatrix vertcat(firstRow, cell2mat(allData)); nodePos allDataMatrix(:, 1:3); varData allDataMatrix(:, 4:end); % 这里假设单元连接在另一个文件或者我们从别的地方读取。 % 如果文件里直接包含单元连接需要按 FIV 格式再单独解析。 elemConn []; end这段代码有几个地方值得展开说一下。第一为什么用textscan加MultipleDelimsAsOne因为Fluent导出的数据列之间可能空格数量不一致可能同时有空格和制表符开启这个选项可以避免解析出多余的NaN。如果不加遇到连续多个空格时textscan会把它当成空值填充导致数据错位。第二为什么第一行要单独处理因为fgetl已经读走了一行后续textscan从当前位置开始读如果直接把第一行丢掉数据就少了一条。我用sscanf(tline, %f)把第一行解析成数值再合并回去这样保证所有数据行都被读入。第三单元连接的处理。上面这段代码假设网格信息在另一个文件里这其实是我实际项目中用的方式从Fluent里分别导出“网格文件”和“变量数据文件”。如果你把单元连接也放到同一个文件那读取逻辑要复杂得多因为文本里会有一段以“(x y z)”括号格式表示的段落而且单元连接部分的列数和变量数据部分不一样解析时要分两个阶段。我自己常用的做法是如果数据文件不带单元连接那就用一个单独的网格文件只导出坐标和连接在Matlab里初始化一次然后后续每个时间步更新变量数据就行。这样省掉了很多重复的内存占用。3.2 批量读取多个时间步文件并整理成数据结构假设你有100个时间步文件文件名是flow_0001.txt到flow_0100.txt可以用下面这个脚本批量读取并组装成一个结构体数组folder ./fluent_results; filePattern fullfile(folder, flow_*.txt); fileList dir(filePattern); % 自然排序把文件名中的数字部分提取出来排序 numList zeros(length(fileList), 1); for i 1:length(fileList) tok regexp(fileList(i).name, (\d), tokens); numList(i) str2double(tok{1}{1}); end [~, sortIdx] sort(numList); fileList fileList(sortIdx); nFiles length(fileList); nCols 7; % X Y Z Vx Vy Vz P results struct(time, cell(1,nFiles), nodePos, cell(1,nFiles), ... varData, cell(1,nFiles)); for k 1:nFiles fileName fullfile(folder, fileList(k).name); [results(k).nodePos, ~, results(k).varData] readFluentASCII(fileName, nCols); % 从文件名或文件内容里提取时间值 tok regexp(fileList(k).name, (\d(\.\d)?), tokens); results(k).time str2double(tok{1}{1}); fprintf(已读取: %s\n, fileList(k).name); end save(transient_results.mat, results, -v7.3);这里有一个很重要的细节-v7.3。Matlab的.mat文件默认用旧版HDF5格式保存如果数据量超过2GB会报错。加上-v7.3后就能支持大文件保存。我建议从一开始保存结果就让这个选项成为习惯不然跑了几十分钟的脚本最后保存失败那滋味真的很难受。dir函数返回的文件列表是按字母序排的所以flow_2.txt会排在flow_10.txt前面。如果你不处理排序后面按时间步绘制的动画就会跳动错乱这个错误很隐蔽尤其是文件多的时候很难一眼看出来。上面的代码里我用regexp提取文件名中的数字并重新排序虽然看上去多写了几行但从长期来看完全值得。3.3 读取HDF5格式的替代方案如果你的数据量大到文本读取无法忍受那HDF5是更好的选择。Fluent新版支持直接输出HDF5格式里面是一个树状结构。用Matlab的h5info和h5read可以很方便地读取fileName transient_result.h5; info h5info(fileName); % 查看顶层有哪些数据集或分组 disp(info.Datasets); % 假设变量 V_x 存在一个名为 /Velocity/X 的数据集里 vxData h5read(fileName, /Velocity/X); % 也可以读取部分数据 vxSlice h5read(fileName, /Velocity/X, [1 1 1], [100 100 1]);HDF5真正的优势是支持沿数据集的局部读取不需要把几十GB的整个文件一次性加载进内存。这在做超大模型后处理时几乎是唯一的选择。比如你只需要某个切面或者某个区域的数据可以在h5read的start和count参数里指定起始索引和读取个数只把那一块数据读出来内存占用可以下降一到两个数量级。不过HDF5也有个门槛你需要先搞清楚Fluent输出到HDF5里的数据结构到底是什么样的。每个版本的Fluent内部命名和层级可能略有不同。我第一次用的时候用h5info一层层看才发现压力数据藏在/Solution/Pressure下面。这种事情没有捷径只能打开h5info仔细翻。3.4 性能优化几十GB数据怎么不把内存撑爆处理大规模瞬态结果时内存管理是头号问题。以1000万单元的网格为例一个变量一个时间步就是1000万×8字节 80MBdouble精度如果是10个变量就是800MB100个时间步全读进来就是80GB绝大多数工作站都会直接崩溃。我有几个常用的优化技巧可以解决大部分内存问题第一个技巧是只保留必要的变量。前面说过导出时只导后处理需要的变量这是从源头控制数据量。如果你已经导出了包含很多变量的文件那在读取函数里也要只提取需要的列别一股脑全塞进结构体里。第二个技巧是用单精度存数据。如果后处理的精度要求不是特别高Matlab里可以用single替代double内存占用直接减半。代价是极少数情况下会出现数值精度问题比如累加统计量时误差偏大。我的建议是波形分析、频谱分析用double稳妥云图展示和动画用single足够。第三个技巧是分批读取和即时统计。比如你要算时均场不需要把100个时间步全部读进来后再平均而可以每读一个时间步就累加到累加器变量里最后只保存平均值和脉动均值。这样内存里始终只需要一个时间步的数据加上两个累加变量。代码逻辑稍微复杂一点但能支撑的计算规模完全不同。第四个技巧是用parfor并行读取不同时间步。如果每步文件独立可以用并行池来加速前提是每个worker只处理自己负责的那几个文件并把最终结果按顺序拼接好。这个方法对多核工作站特别有效我实测过16核机器上可以做到3-4倍的加速。4. 瞬态结果后处理的几个典型实操场景4.1 一键生成瞬态云图动画GIF/视频把时间步云图连成动画是最直观也最常见的后处理需求。以二维温度场为例假设网格是规则的节点坐标和温度值已经读到结构体数组里可以这样生成GIFfigure(Position, [100 100 800 600]); for k 1:nFiles x results(k).nodePos(:, 1); y results(k).nodePos(:, 2); T results(k).varData(:, 3); % 假设第三列是温度 % 先把散点插值到规则网格或者直接用 scatter scatter(x, y, 10, T, filled); axis equal tight; colormap(jet); colorbar; caxis([tMin tMax]); % 固定色标范围否则会闪变 title(sprintf(t %.3f s, results(k).time)); drawnow; % 捕获当前帧写入 GIF frame getframe(gcf); [A, map] rgb2ind(frame2im(frame), 256); if k 1 imwrite(A, map, transient_anim.gif, gif, LoopCount, Inf, DelayTime, 0.05); else imwrite(A, map, transient_anim.gif, gif, WriteMode, append, DelayTime, 0.05); end end这里面最容易被忽略的是caxis([tMin tMax])这一行。如果你不固定色标范围每一帧就会根据该帧的最大最小值自动调整颜色映射导致背景区域颜色不断变化动画看起来很“闪”。一定要在循环外先算好整个时间序列T的最小值和最大值再在循环里固定下来这样动画的颜色才是稳定可比的。如果目标不是GIF而是视频可以用VideoWriter在循环里写writeVideo。视频的优点是压缩率高、文件体积小适合放进汇报PPT里。4.2 提取监测点的时间序列从网格到点Fluent里可以设置监测点直接导出某点的时间序列。但如果你的模型已经算完没有提前设置监测点也可以从已有的瞬态结果里“插值”出来。这个思路在风工程、传热分析里特别常用比如你想知道某个特定位置的温度变化而这个位置恰好没有网格节点。要用Matlab做这件事scatteredInterpolant是最方便的工具。它可以把散点数据构造成一个插值函数然后在任意坐标位置求值% 以第一个时间步的网格为基础建立插值器 F scatteredInterpolant(results(1).nodePos(:,1), ... results(1).nodePos(:,2), results(1).nodePos(:,3), ... results(1).varData(:, 3), linear, none); % 指定监测点坐标 pt [0.015, 0.02, 0.0]; timeSeries zeros(nFiles, 1); for k 1:nFiles F.Values results(k).varData(:, 3); % 更新温度值 timeSeries(k) F(pt(1), pt(2), pt(3)); end plot([results.time], timeSeries, b-o); xlabel(t (s)); ylabel(T (K));注意scatteredInterpolant对象的复用方式先创建对象然后在循环里更新Values属性而不是每次重新创建插值器。创建插值器需要构建三角剖分这是比较耗时的操作。如果循环里每次都新建100个时间步就要构建100次完全没必要。如果监测点坐标落在计算域之外插值函数会返回NaN所以建议在做插值之前先判断一下点是否在网格范围内或者用nearest方法作为兜底。4.3 时均场与脉动场计算搞湍流必做处理湍流或周期性流动时时均场和脉动场是最基本的统计量。把N个时间步的标量场记为(\phi_k(x))时均场的计算公式是[ \bar{\phi}(x) \frac{1}{N} \sum_{k1}^{N} \phi_k(x) ]脉动场就是瞬时值与时均值的差。在Matlab里实现时不需要把所有瞬时场都存下来。你可以用两组累加变量一组累加(\phi)另一组累加(\phi^2)。全部读完后再算均值和均方根nVars size(results(1).varData, 2); sumPhi zeros(size(results(1).varData)); sumPhiSq zeros(size(results(1).varData)); for k 1:nFiles phi results(k).varData; sumPhi sumPhi phi; sumPhiSq sumPhiSq phi.^2; end meanPhi sumPhi / nFiles; rmsPhi sqrt(sumPhiSq / nFiles - meanPhi.^2);这里有个数值稳定性问题值得注意如果(\phi)的数量级很大比如压力是几万Pa那么(\phi^2)会达到十亿级别累加时可能出现浮点精度损失。解决办法是把数据先减去一个参考值比如第一帧的平均值再累加平方项。我在计算压力脉动时用过这个方法效果很明显。如果你的变量量级本身不是特别大这个处理也可以省略。4.4 频谱分析从监测点时间序列找主频做完时间序列提取后做FFT是顺理成章的事。以涡脱落频率分析为例假设监测点速度时间序列已经存到timeSeries向量里时间步长是(\Delta t)。FFT代码如下Fs 1 / dt; % 采样频率 L length(timeSeries); Y fft(timeSeries - mean(timeSeries)); % 去掉直流分量 P2 abs(Y / L); P1 P2(1:floor(L/2)1); P1(2:end-1) 2 * P1(2:end-1); f Fs * (0:(L/2)) / L; plot(f, P1); xlabel(频率 (Hz)); ylabel(幅值);FFT的前提是时间序列等间隔采样。Fluent导出的时间步通常都是等间隔的但如果你在计算中使用了自适应时间步那导出的时间步长可能不相等这时候直接做FFT是不对的。解决办法是把数据插值到等间隔时间轴上可以用resample或interp1。另外一个经验是时间序列长度最好是2的幂次FFT最稳妥。如果长度不是可以补零到512、1024、2048这样的长度虽然不增加真实频谱分辨率但能让频谱看起来更平滑。5. 常见问题与排查技巧实录5.1 数据读取错位、乱码、精度缺失这类问题通常表现为画出来的云图位置明显错乱、值域范围完全不合理、或者某些点出现NaN。排查时我建议按下面顺序来首先检查文件编码。Fluent导出的ASCII文件不同版本可能使用不同编码最常见的是UTF-8和本地系统编码。如果Matlab用默认编码读取后出现乱码可以在fopen时指定n,UTF-8参数。其次检查分隔符。前面提过Fluent导出的列分隔符可能是空格也可能是制表符甚至不同行之间空格数不一致。用textscan时一定要开启MultipleDelimsAsOne并且在读入后用any(isnan(allDataMatrix(:)))检查一遍有没有缺失值。最后检查列数是否匹配。如果Fluent导出的变量比你预期的多一列或少一列那所有数据都会整体错位。这种错误最恶心的地方在于它不会报错只是结果不对。我的一个习惯是读取完成后打印size(allDataMatrix)看一眼行列数是否符合预期再绘制第一帧云图做个直观检查。5.2 内存不足与OOM处理如果你遇到“Out of Memory”错误先从这几个角度排查一是是否一次性读入了所有时间步。建议改成前面说的“分布式处理”模式读一步处理一步而不是把所有结果都堆在结构体里。二是是否过度使用双精度。Fluent导出的变量值很多都是float32精度Matlab默认会以double类型存储如果数据量巨大可以考虑在读取时显式转成single。三是是否有变量残留。Matlab的clear不是立即释放内存而是标记为可回收。如果循环里确实不需要某一步的数据了用clear之后再调用pack效果并不明显最稳妥的办法是避免让不用的数据一直占用变量名。比如你在循环里先读了第k步数据计算完统计量后直接覆盖phi变量而不是往一个数组里不断追加。5.3 单元连接顺序与可视化错乱如果你要基于单元数据做后处理比如计算面通量、体积分单元连接关系的正确读取至关重要。Fluent导出的单元连接默认是按Fluent内部的单元编号顺序给出的。如果你把节点坐标和单元连接组装成patch或triangulation类型时顺序不对画出来的网格会非常混乱可能出现“面条式”交叉网格。解决思路是在读取网格信息后用triangulation对象画一次网格图检查是否有明显的几何异常。如果出现交叉、穿越多半是单元连接的节点顺序不是按逆时针或顺时针排列导致的。Matlab的patch函数需要单元连接中的节点是逆时针排列才能在着色时正确显示法向。这种情况下需要对连接矩阵做一次排序调整。比较省事的办法是用triangulation对象它内部会自动处理一些拓扑问题如果仍然异常再逐单元检查前几个单元的坐标连接是否合理。5.4 配套技巧快速验证读取结果是否正确读取数据这件事越早发现错误越好。我每次写完读取代码不会直接进入复杂的后处理而是先做三件小事第一打印前3行数据人工核对坐标和变量值是否合理比如坐标在几何范围内、速度量级符合物理直觉。第二画第一帧的云图大致看一眼分布有没有明显的离谱区域比如某个角落温度几百万度、压力出现负数爆表。第三检查时间序列的连续性。画某个监测点的时间序列曲线如果曲线剧烈震荡跳变多半是数据文件顺序读反了或者单位换算没做对。这三步加起来不到两分钟但能省掉后面数小时的排错时间。我见过有的朋友花了一个下午去调动画结果最后发现是读取顺序错了所有帧的顺序都反了这种教训实在不值得重蹈。6. 从这套流程里能带走的东西关于Matlab读取Fluent瞬态结果这件事我最近一次做项目时又踩了一遍“导出变量太多导致文件巨大”的坑。后来静下心来把上面这套流程重写了一遍从原来一个case处理2小时缩短到15分钟主要就是靠导出前克制变量数量、读一遍网格复用到所有时间步、以及用单精度存储中间结果。一个很大的体会是这个问题的核心往往不在Matlab代码本身而在于你一开始怎么组织Fluent的导出。数据导出的格式、命名规则、变量选择直接决定了Matlab后续读取的复杂度和性能。在项目开始前先花10分钟规划一下整个数据处理流程比在后期拼命优化脚本要有效得多。另外处理这类工程数据时尽量把读取函数和后处理函数分开写。因为你会发现这个case用过的读取函数下个case改改路径和列数就能复用到而后处理函数写得好一点一套代码可以服务很多个模型。以模块化的方式组织代码你后续迭代的速度会快很多。如果你正在做类似的工作建议直接把文中的代码框架拿去改改用你自己的数据先跑通“读取→动画→统计”这条最小流程再逐步扩展功能。过程中遇到具体问题欢迎随时交流我会尽力分享我的经验。