1. 从“xlsread”到“readtable”MATLAB读取Excel数据的演进与选择如果你还在用xlsread这个函数来读取Excel数据我得说你可能已经落后于MATLAB官方推荐的最佳实践好几年了。这就像现在还在用Windows XP一样不是不能用但总会遇到一些意想不到的兼容性问题和性能瓶颈。我见过太多工程师和科研人员因为习惯了老代码或者从网上抄了一段xlsread的示例就一直沿用下去直到某天遇到一个包含中文、公式或者特殊格式的新Excel文件程序突然报错或者读出来的数据面目全非才手忙脚乱地开始排查。MATLAB读取Excel远不止“打开文件、读取数据”这么简单。它涉及到编码兼容性、数据类型自动识别、性能优化尤其是面对动辄几十万行的大数据文件时以及如何优雅地处理表头、缺失值和混合数据类型列。网络上搜索“MATLAB 读取Excel”时伴随出现的“完整代码”、“xlsread”等热词恰恰反映了大家对这个基础但关键操作的需求和困惑。今天我就结合自己处理过上百个数据文件的经验帮你彻底理清思路从过时的xlsread过渡到更强大、更稳健的readtable和readmatrix并附上能应对各种“奇葩”Excel文件的实战代码。2. 为什么你应该放弃xlsread历史、局限与替代方案xlsread是MATLAB一个非常古老的函数它的设计初衷是为了兼容早期的.xls格式Excel 97-2003。在很长一段时间里它是读取Excel的唯一选择。但是随着Excel升级到.xlsx格式基于XML的Open XML格式以及数据复杂度的提升xlsread的局限性越来越明显。首先最核心的问题是函数签名和返回值混乱。xlsread的基本调用格式是[num, txt, raw] xlsread(filename)。num返回数值数据txt返回文本数据raw返回所有原始数据单元格格式。这个设计导致了一个非常尴尬的局面如果你的数据是混合的比如第一列是文本ID后面是数值你需要手动拼接num和txt或者去处理结构复杂的raw元胞数组。这个过程极易出错代码可读性也差。其次对现代Excel文件特性支持孱弱。xlsread在处理.xlsx文件时实际上是调用一个后台的COM接口在Windows上或第三方库在非Windows系统上。这导致性能低下对于大型文件初始化COM接口的开销巨大。热词中“python读取excel数据全部读取耗时5分钟”虽然说的是Python但MATLAB的xlsread在大文件上同样表现不佳。兼容性陷阱它可能无法正确读取由新版Excel如Office 365创建或包含特殊函数、格式的表格。有时甚至会因为系统注册表问题如热词中提到的“无法读取 usbperf\performance 注册表项”而导致整个MATLAB崩溃或“闪退”。功能缺失它很难指定读取的准确范围除了简单的矩形区域对于处理多个工作表Sheet或需要跳过表头、注释行的情况配置起来非常别扭。那么替代品是什么从R2019a开始MathWorks官方就明确推荐使用readtable和readmatrix等函数作为读取表格数据包括Excel的首选。它们基于一个更现代、更统一的导入工具框架支持格式更广泛.xls, .xlsx, .xlsm, .xltx, .xltm性能更好且API设计清晰。readtable当你希望将Excel数据读取为一个表格table变量时使用。这是最常见的情况因为表格可以完美地存储列名表头、混合数据类型每列类型可以不同并且支持强大的表格索引和数据处理操作。readmatrix当你确定Excel工作表中只包含同质的数值数据或可转换为数值的数据并且你不需要表头信息只想要一个纯粹的数值矩阵matrix时使用。它的速度通常比readtable更快。readcell如果你需要读取最原始的数据包括所有格式、公式结果作为文本并存储在一个元胞数组cell array中可以使用它。这类似于xlsread的raw输出但接口更一致。注意在Mac或Linux系统上xlsread的功能受限更为严重。而readtable等函数在这些平台上的表现更加一致和可靠。如果你的代码需要考虑跨平台运行那么弃用xlsread是必须的。3. 使用readtable读取Excel从基础到高级控制readtable是处理Excel表格数据的瑞士军刀。它的核心思想是将Excel的每一列映射为表格的一列第一行默认作为变量名列名。让我们从一个最简单的例子开始。3.1 基础读取与关键参数假设你有一个名为‘sales_data.xlsx’的文件里面有一个‘Quarterly’工作表数据从A1单元格开始。% 最基本用法读取指定文件的第一个工作表 dataTable readtable(‘sales_data.xlsx‘); % 指定工作表名称 dataTable readtable(‘sales_data.xlsx‘, ‘Sheet‘, ‘Quarterly‘); % 指定工作表序号 dataTable readtable(‘sales_data.xlsx‘, ‘Sheet‘, 2);读取后dataTable是一个表格变量。你可以用dataTable.Properties.VariableNames查看列名用dataTable(1:5, :查看前5行或者直接用dataTable.Revenue访问名为‘Revenue‘的列。但是现实中的数据很少这么“干净”。你的Excel文件可能前几行是标题、注释或者数据并非从A1开始。这就需要用到readtable更强大的参数。% 场景1数据从第5行、第2列B列开始 opts detectImportOptions(‘sales_data.xlsx‘); % 先探测导入选项 opts.DataRange ‘B5‘; % 设置数据起始单元格 dataTable readtable(‘sales_data.xlsx‘, opts); % 场景2文件有3行标题我们需要跳过 opts detectImportOptions(‘sales_data.xlsx‘); opts.VariableNamesLine 4; % 将第4行作为列名行 opts.DataLines [5, Inf]; % 数据从第5行开始到文件末尾 % 或者更简洁地直接设置要跳过的行数 dataTable readtable(‘sales_data.xlsx‘, ‘NumHeaderLines‘, 3);detectImportOptions是一个非常有用的函数它会自动分析你的文件结构生成一个包含各种猜测的选项对象SpreadsheetImportOptions。你可以在此基础上进行微调然后再传给readtable。这比直接硬编码参数要灵活和健壮得多。3.2 处理混合数据类型与缺失值Excel列中经常混合着数字、文本和空单元格。readtable会尝试为每一列推断最合适的数据类型。但有时它的推断会出错比如把一列以数字开头的产品编码如 ‘001A‘, ‘002B‘错误地推断为数字导致前面的零丢失。这时你需要手动指定列的数据类型。opts detectImportOptions(‘product_list.xlsx‘); % 假设我们知道第1列是文本产品编码第3列是分类数据产品类别 opts setvartype(opts, {‘ProductID‘, ‘Category‘}, {‘string‘, ‘categorical‘}); % 假设第2列‘Price‘应该是double但文件里有些单元格写着‘N/A‘ opts setvartype(opts, ‘Price‘, ‘double‘); % 对于‘N/A‘这样的缺失值可以指定如何对待 opts.MissingRule ‘fill‘; % 遇到无法转换的值如‘N/A‘时用缺失值填充 opts setvaropts(opts, ‘Price‘, ‘TreatAsMissing‘, {‘N/A‘, ‘--‘}); % 指定哪些字符串应被视为缺失 dataTable readtable(‘product_list.xlsx‘, opts);处理之后dataTable.Price中原来的 ‘N/A‘ 会变成NaNNot a Number这是MATLAB中表示数值缺失的标准方式。对于文本或分类列缺失值则是missing。3.3 性能优化只读取需要的部分当Excel文件非常大时全部读取会消耗大量内存和时间。readtable允许你进行“选择性读取”。% 只读取特定的列 opts detectImportOptions(‘huge_dataset.xlsx‘); opts.SelectedVariableNames {‘Date‘, ‘Sensor1‘, ‘Sensor2‘, ‘Result‘}; % 只读这四列 dataTable readtable(‘huge_dataset.xlsx‘, opts); % 只读取特定的行范围例如前10000行 opts.DataLines [1, 10000]; dataTable readtable(‘huge_dataset.xlsx‘, opts); % 结合两者读取A到D列第1000到20000行 % 这需要更底层的‘Range‘参数它直接指定Excel的区域 dataTable readtable(‘huge_dataset.xlsx‘, ‘Range‘, ‘A1000:D20000‘);一个重要的性能提示对于超大型的.xlsx文件如果它主要包含数值数据且你不需要表头或混合类型处理那么使用readmatrix并指定范围速度会快上一个数量级。因为readtable需要做更多的元数据解析和类型推断。4. 使用readmatrix读取纯数值数据当你处理传感器数据如热词中的MPU6050、ICM42688、仿真输出如Vivado仿真波形导出或数值矩阵时readmatrix是你的最佳选择。它省去了所有表格格式处理的 overhead直接返回一个双精度矩阵。% 读取整个工作表的数值数据 numericData readmatrix(‘sensor_log.xlsx‘); % 指定工作表 numericData readmatrix(‘sensor_log.xlsx‘, ‘Sheet‘, ‘Accelerometer‘); % 指定读取区域Excel范围表示法 % 读取‘Sheet2‘中从B2到G1001的矩形区域 numericData readmatrix(‘sensor_log.xlsx‘, ‘Sheet‘, 2, ‘Range‘, ‘B2:G1001‘); % 如果数据区域不是从单元格开始可以使用‘OutputType‘参数确保输出为double numericData readmatrix(‘mixed_data.xlsx‘, ‘OutputType‘, ‘double‘);readmatrix会尝试将所有内容转换为double。如果某个单元格是无法转换的文本比如一个字母它会在那个位置放入NaN。这对于处理偶尔带有注释行的数据日志非常方便。与xlsread的对比readmatrix的功能大致对应xlsread的num输出但接口更清晰性能更好且跨平台行为一致。你不再需要处理那三个令人困惑的返回值。5. 实战代码合集应对各种复杂场景光讲原理不够下面我提供几段“即抄即用”的完整代码块覆盖了最常见的几种复杂场景。你可以把这些代码保存为脚本或函数根据你的文件名和需求稍作修改。5.1 场景一读取带有多个标题行和注释的报表文件monthly_report.xlsx结构如下第1行大标题无用第2行制表日期无用第3行空行第4行真正的列名表头第5行开始数据function dataTable readComplexExcelReport(filename) % 读取具有多行标题的复杂Excel报表 % 输入filename - Excel文件路径 % 输出dataTable - 清理后的表格数据 % 1. 创建导入选项并探测 opts detectImportOptions(filename); % 2. 手动设置跳过前3行标题、日期、空行第4行作为列名 opts.VariableNamesLine 4; % 第4行包含变量名 opts.DataLines [5, Inf]; % 数据从第5行开始 % 3. 读取数据 dataTable readtable(filename, opts); % 4. 后处理清理可能因格式产生的多余变量名如‘Var1‘ % 检查是否有默认的‘Var‘开头的列名这通常意味着表头读取有问题 varNames dataTable.Properties.VariableNames; if any(startsWith(varNames, ‘Var‘)) warning(‘检测到可能未正确读取的列名。请检查Excel文件第4行的表头格式。‘); % 可以选择手动重命名例如 % dataTable.Properties.VariableNames {‘Date‘, ‘Department‘, ‘Revenue‘, ‘Expense‘}; end % 5. 处理缺失值将表格中的错误值或空字符串替换为标准的缺失类型 for i 1:width(dataTable) col dataTable{:, i}; if iscell(col) % 如果是元胞数组列可能包含文本 % 将空字符串元胞替换为missing emptyCells cellfun((x) ischar(x) isempty(x), col); if any(emptyCells) col(emptyCells) {missing}; dataTable{:, i} col; end end end fprintf(‘成功从 [%s] 读取了 %d 行 %d 列数据。\n‘, ... filename, height(dataTable), width(dataTable)); end5.2 场景二高效读取大型数据文件中的特定列和行假设你有一个巨大的实验数据文件experiment_2024.xlsx你只关心其中几个传感器的数据并且只需要分析从某个时间戳之后的数据比如第5000行之后。function sensorData readLargeSensorData(filename, sheetName, startRow) % 从大型Excel文件中高效读取特定传感器数据 % 输入 % filename - 文件路径 % sheetName - 工作表名可选默认为第一个 % startRow - 开始读取的行号可选默认为1 % 输出 % sensorData - 包含所需传感器数据的表格 if nargin 2 sheetName 1; % 默认第一个工作表 end if nargin 3 startRow 1; end % 定义我们感兴趣的传感器列假设列名已知 targetColumns {‘Timestamp‘, ‘Accel_X‘, ‘Accel_Y‘, ‘Accel_Z‘, ‘Gyro_X‘, ‘Gyro_Y‘, ‘Gyro_Z‘, ‘Temperature‘}; % 创建导入选项并立即限制变量 opts detectImportOptions(filename, ‘Sheet‘, sheetName); opts.SelectedVariableNames targetColumns; opts.DataLines [startRow, Inf]; % 从指定行读到末尾 % 为了提高大文件读取速度可以预设变量类型避免自动探测开销 % 假设我们知道所有目标列都是数值型 opts setvartype(opts, targetColumns, ‘double‘); % 执行读取 sensorData readtable(filename, opts); % 可选将‘Timestamp‘列转换为MATLAB的datetime类型便于时间序列分析 if ismember(‘Timestamp‘, targetColumns) % 假设时间戳是Excel序列日期常见格式 try sensorData.Timestamp datetime(sensorData.Timestamp, ‘ConvertFrom‘, ‘excel‘); catch warning(‘时间戳列转换失败保持原样。请检查时间戳格式。‘); end end fprintf(‘从第%d行开始读取了%d行传感器数据。\n‘, startRow, height(sensorData)); end5.3 场景三批量读取多个相同结构Excel文件并合并这是数据分析中非常常见的任务每个月/每个实验都有一个独立的Excel文件你需要把它们全部读进来合并成一个总表。function mergedTable batchReadAndMergeExcel(filePattern) % 批量读取匹配模式的所有Excel文件并合并 % 输入filePattern - 文件匹配模式如 ‘data_*.xlsx‘ 或 ‘folder/*.xlsx‘ % 输出mergedTable - 合并后的总表格 % 1. 获取所有匹配的文件列表 fileList dir(filePattern); if isempty(fileList) error(‘未找到匹配模式 [%s] 的文件。‘, filePattern); end % 2. 初始化一个元胞数组来存储每个表格 allData cell(numel(fileList), 1); % 3. 循环读取每个文件 for i 1:numel(fileList) currentFile fullfile(fileList(i).folder, fileList(i).name); fprintf(‘正在读取 (%d/%d): %s\n‘, i, numel(fileList), currentFile); try % 使用统一的读取选项确保结构一致 opts detectImportOptions(currentFile); % 假设所有文件的第一行都是表头数据从第二行开始 opts.DataLines [2, Inf]; tempTable readtable(currentFile, opts); % 可选添加一列记录数据来源 tempTable.FileSource repmat(string(fileList(i).name), height(tempTable), 1); allData{i} tempTable; catch ME warning(‘读取文件 [%s] 时出错: %s\n跳过此文件。‘, ... currentFile, ME.message); allData{i} []; % 存入空值 end end % 4. 移除读取失败的空项 allData(cellfun(isempty, allData)) []; if isempty(allData) error(‘所有文件读取均失败无法合并。‘); end % 5. 垂直合并所有表格 % 使用‘vertcat‘函数要求所有表格具有相同的变量名和类型 mergedTable vertcat(allData{:}); fprintf(‘批量读取完成共合并了 %d 个文件总计 %d 行数据。\n‘, ... numel(allData), height(mergedTable)); end6. 避坑指南与高级技巧在实际操作中你肯定会遇到一些令人头疼的问题。下面是我总结的几个常见“坑”及其解决方案。坑1中文或特殊字符乱码这个问题通常出现在文件路径、工作表名或单元格内容包含非英文字符时。readtable对UTF-8的支持在现代MATLAB版本中已经很好但为了确保万无一失可以尝试以下方法确保MATLAB的工作区编码是UTF-8。可以在命令行输入feature(‘DefaultCharacterSet‘)查看如果不是在脚本开头尝试feature(‘DefaultCharacterSet‘, ‘UTF-8‘)但此命令不一定总是有效取决于操作系统。最根本的解决方案是在保存Excel文件时选择“文件”-“另存为”在保存对话框中点击“工具”-“Web选项”然后在“编码”选项卡中选择“Unicode (UTF-8)”。但这需要你能控制Excel文件的生成。如果乱码发生在列名上可以在读取后使用dataTable.Properties.VariableNames直接重命名列。坑2数字被误读为文本特别是以0开头的编号Excel经常自作聪明地把看起来像数字的文本如产品编号‘001‘显示为数字存储时也当作数字。当MATLAB读取时就失去了前面的零。预防在Excel中在输入这类数据前先将单元格格式设置为“文本”或者输入时前面加一个单引号如‘001。补救如果已经读进来了数字1变成了双精度1前面的零无法恢复。你只能在读取时通过setvartype强制将该列指定为‘string‘或‘char‘类型但前提是Excel文件中该列的数据类型没有被破坏。有时需要先用readcell读取原始内容看看。坑3读取速度极慢对于超过几十万行的大型文件读取速度可能很慢。首要优化使用readmatrix代替readtable如果数据结构允许。其次使用SelectedVariableNames和DataLines/Range精确限定读取范围避免读取不必要的数据。检查系统关闭其他占用大量内存和CPU的程序。确保MATLAB有足够的内存可用memory命令查看。对于超大型文件考虑将Excel文件拆分成多个小文件或将其转换为更高效的格式如.mat、.csv或.parquetR2021b及以上版本支持parquetread。坑4依赖特定Excel环境导致程序崩溃你的代码在你自己电脑上运行良好但在没有安装Excel或MATLAB运行时的服务器上就崩溃。根因readtable在Windows上默认可能仍会尝试使用COM接口处理某些复杂格式这依赖于本地的Excel安装。解决方案强制MATLAB使用其内置的纯Java/本地库来读取。这可以通过设置导入选项实现opts detectImportOptions(‘file.xlsx‘); % 尝试设置为‘UseExcel‘为false但这取决于文件类型和MATLAB版本 % 更可靠的方法是如果可能要求数据提供者将文件保存为‘Excel 97-2003 工作簿 (*.xls)‘格式 % 或者更好的办法是保存为CSV格式然后用 readtable 读取CSV其兼容性是最好的。高级技巧处理多个工作表Sheet有时你需要读取一个工作簿里的所有工作表。filename ‘multi_sheet_data.xlsx‘; % 获取所有工作表名 [~, sheetNames] xlsfinfo(filename); % xlsfinfo仍然有用 % 创建一个结构体或元胞数组来存储每个工作表的数据 allSheetsData struct(); for i 1:length(sheetNames) opts detectImportOptions(filename, ‘Sheet‘, sheetNames{i}); % 可以对每个sheet设置不同的选项比如有的sheet表头在第2行 % if strcmp(sheetNames{i}, ‘Summary‘) % opts.DataLines [2, Inf]; % end allSheetsData.(genvarname(sheetNames{i})) readtable(filename, opts); end % 现在可以通过 allSheetsData.Sheet1, allSheetsData.Summary 来访问各个表genvarname函数用于将工作表名转换为有效的MATLAB结构体字段名例如去掉空格和非法字符。7. 从文件到分析数据读取后的第一步处理成功将数据读入MATLAB工作区只是万里长征第一步。读取后的数据往往需要经过清洗和预处理才能用于分析。这里给出几个最常见的后续操作。1. 探索性查看% 查看表格概览变量名、类型、前几行数据 summary(dataTable) % 或者 head(dataTable) % 显示前几行 tail(dataTable) % 显示后几行 % 查看大小 [numRows, numCols] size(dataTable); fprintf(‘数据表有 %d 行 %d 列。\n‘, numRows, numCols);2. 处理缺失值表格中的缺失值会显示为NaN数值列或missing文本列。% 找出包含缺失值的行 rowsWithMissing ismissing(dataTable); % 统计每列的缺失值数量 missingCount sum(ismissing(dataTable)); % 方案A删除包含任何缺失值的行 dataClean rmmissing(dataTable); % 默认删除包含缺失值的行 % 方案B仅删除在关键列上有缺失的行 dataClean rmmissing(dataTable, ‘DataVariables‘, {‘CriticalColumn1‘, ‘CriticalColumn2‘}); % 方案C用特定值填充缺失值例如用列均值填充数值列 meanVal mean(dataTable.NumericColumn, ‘omitnan‘); dataTable.NumericColumn(isnan(dataTable.NumericColumn)) meanVal;3. 数据类型转换与计算% 确保数值列是正确的类型有时会被误读为元胞数组 if iscell(dataTable.ColumnName) dataTable.ColumnName cell2mat(dataTable.ColumnName); end % 将文本日期列转换为datetime类型 dataTable.DateColumn datetime(dataTable.DateColumn, ‘InputFormat‘, ‘yyyy-MM-dd‘); % 进行简单的计算添加新列 dataTable.RevenuePerUnit dataTable.TotalRevenue ./ dataTable.UnitsSold;4. 数据筛选% 使用逻辑索引进行筛选高效且直观 % 筛选出‘Department‘为‘Sales‘且‘Revenue‘大于10000的行 highSales dataTable(strcmp(dataTable.Department, ‘Sales‘) dataTable.Revenue 10000, :); % 使用 findgroups 和 splitapply 进行分组操作类似Excel的数据透视表 [G, departments] findgroups(dataTable.Department); totalByDept splitapply(sum, dataTable.Revenue, G); resultTable table(departments, totalByDept, ‘VariableNames‘, {‘Department‘, ‘TotalRevenue‘});读取Excel数据是数据分析流水线的入口。一个健壮、高效的读取流程能为后续所有工作打下坚实的基础。放弃古老的xlsread拥抱readtable和readmatrix并学会利用detectImportOptions进行精细控制你会发现处理Excel文件从此变得清晰而强大。记住关键不是记住所有参数而是理解“探测选项 - 微调 - 读取”这个工作流以及如何根据数据特点大小、类型、整洁度选择最合适的工具和方法。当你再遇到“MATLAB闪退”或“读取数据不对”的问题时希望这篇文章里的思路和代码能帮你快速定位到症结所在。