☰
Matlab读取Excel进化指南:从xlsread到readtable的完整实践
2026/10/9 4:00:47 网站建设 项目流程

从xlsread到readtable,Matlab读取Excel的完整进化指南

在Matlab里折腾Excel数据导入这件事,看起来很简单,但你真去用的时候会发现坑一个接一个。早期版本只有xlsread,后来官方力推readtable,再后来又有readmatrix、readcell这一套,每个函数背后代表的是完全不同的设计思路。如果你只是机械地记住某个函数能用,而不知道它们之间的取舍,遇到复杂Excel文件就会卡壳。

这篇文章我按照实际工作中总结的经验,把Matlab读取Excel的完整路径捋一遍:从最基础的函数选型、到多Sheet批量导入、再到数据清洗和性能优化,最后附上我踩过的一系列坑。不管你是刚接触Matlab的新手,还是已经写了不少脚本的老手,这篇文章里应该都有你能直接拿走用的东西。

1. 基础导入函数选型:四个函数,各自背后的设计逻辑

1.1 xlsread:经典但衰老的方案,新手最容易用错

很多教程一上来就教xlsread,但这其实是历史遗留方案。xlsread的调用格式是[num, txt, raw] = xlsread(filename, sheet, range),它返回三个输出:数值矩阵num、文本矩阵txt、原始数据raw。这个三输出设计听起来很灵活,实际上非常难用。

先看一个典型场景:Excel表格第一行是列名,后面是数据。用xlsread读这种表格,num里只有数值部分,txt里只有文本部分,raw是混合原始内容。问题在于,你需要自己判断哪些列是数值、哪些是文本,然后手动拼接,稍微复杂一点的数据结构,代码就充满了判断逻辑。

% xlsread的典型用法(不推荐) [num, txt, raw] = xlsread('data.xlsx', 'Sheet1', 'A1:F100'); % num是数值矩阵,txt是单元格字符串,raw是原始内容 % 但你需要自己写逻辑把这三块拼回去

如果你只是读一个纯数值表格,xlsread勉强能用。但一旦表格里有列名、有文本描述、有混合类型,xlsread的三输出模式会让你非常痛苦。而且xlsread底层依赖的是老旧的Excel COM接口实现,在部分环境里读取速度很慢,遇到.xlsx格式还有兼容性问题。

1.2 readtable:现代主力,自动类型推断是核心优势

readtable是我现在最推荐的方案,它的设计逻辑和xlsread完全不同。readtable把整个Excel当作一个表格对象读入,每一列自动识别类型:数值列变成double、文本列变成cell或string、逻辑列变成logical。这一步就把xlsread需要手动处理的类型判断自动化了。

% readtable的基础用法 T = readtable('data.xlsx'); % 自动处理列名、类型推断 % 也可以通过参数精确控制 T = readtable('data.xlsx', 'Sheet', 'Sheet2', 'Range', 'A1:E20');

readtable返回的table数据结构很适合后续的数据分析。你可以直接用T.ColumnName提取列数据,用T(1:10, :)切片筛选,配合tall数组、varfun、rowfun这些函数,整个数据处理链路会顺畅很多。

1.3 readmatrix和readcell:作为补充方案的适用场景

readmatrix直接返回数值矩阵,适合纯数值数据、不需要列名和表头处理的场景。readcell返回单元格数组,保留所有原始内容的类型,适合异构数据混杂的场合。

% readmatrix:纯数值数据的快速读取 M = readmatrix('num_data.xlsx'); % readcell:保留所有原始数据类型的读取 C = readcell('mix_data.xlsx');

这三个函数的选用逻辑,可以归纳为一句话:要数据分析、列名处理、类型自动识别,用readtable;要纯数值矩阵,用readmatrix;要保留原始单元格内容,用readcell;xlsread建议在新代码中淘汰。

1.4 函数选型速查表

场景推荐函数返回类型备注
常规数据分析readtabletable类型自动识别,最推荐
纯数值矩阵readmatrixdouble矩阵速度快,内存友好
混合数据类型保留readcellcell不丢失原始信息
兼容旧代码xlsreadnum/txt/raw我建议尽快迁移
大数据集datastoretall延迟读取,超大数据用

有一点要提醒:如果你用tab或dlmread这类纯文本读取函数去读Excel,大概率会出问题。Excel的.xlsx本质上是压缩包格式,不是纯文本,所以必须走Matlab专门的Excel接口层。

2. 进阶数据读取:多Sheet、多文件与数据清洗

2.1 批量读取多个Sheet的完整方案

实际工作中,单个Excel文件往往有多个Sheet,而且Sheet名称不固定,有的叫“原始数据”,有的叫“Sheet1”。这时候你需要先看清楚文件里有哪些Sheet,再决定怎么读。

% 获取Excel文件的所有Sheet名称 [~, sheetNames] = xlsfinfo('data.xlsx'); disp(sheetNames); % 批量读取所有Sheet到struct里 allSheets = struct(); for i = 1:length(sheetNames) allSheets.(sheetNames{i}) = readtable('data.xlsx', 'Sheet', sheetNames{i}); end

如果你用的是xlsfinfo,反馈回来的sheetNames是单元格数组,直接拿来做循环索引很方便。用struct存储每个Sheet的table,后续访问直接allSheets.原始数据或者allSheets.('Sheet1')都行。

关于Sheet的具体读取范围,有一个小技巧:如果你不需要整个Sheet,用Range参数精确指定区域,能显著减少内存占用和读取时间。比如只读A1:F100,就不要让Matlab读全Sheet再裁剪,纯属浪费。这里要说明的是,这种指定区域的读取方式,在不同Matlab版本上表现基本一致,实测下来不会有兼容问题。

2.2 文件夹内多个Excel文件的批量导入

这是数据处理项目里最常遇到的场景之一:一个文件夹里堆了几十上百个Excel文件,格式类似,但文件数量多,不可能手动一个一个读。

% 方式一:用dir列出所有xlsx文件,循环读取 files = dir(fullfile('data_folder', '*.xlsx')); allData = cell(length(files), 1); for i = 1:length(files) filePath = fullfile(files(i).folder, files(i).name); allData{i} = readtable(filePath); end % 方式二:用datastore,适合大规模数据延迟加载 ds = datastore('data_folder/*.xlsx'); while hasdata(ds) T = read(ds); % 逐块处理 end

两种方式的适用场景不同:文件数量不算多(几十个)、每个文件不大时,用dir循环最简单直观;但如果Excel文件非常多(几百上千个)、单个文件也大,直接用datastore会更稳——datastore不会一次性把所有数据载入内存,而是一块一块读,避免内存爆掉。

批量导入之后,还有个容易忽略的细节:文件命名规范。如果你在读取时需要区分数据来源,建议循环里把文件名也记录下来。

allData{i}.sourceFile = string(files(i).name); % 在每行都标记来源

2.3 对导入后的数据进行统一清洗

Excel里的数据基本都有问题:空单元格、前导空格、数值被存成文本、日期格式混乱。我整理了一套比较通用的清洗流程,直接按顺序操作就好。

T = readtable('raw_data.xlsx'); % 第一步:删除全空行和全空列 T = rmmissing(T); % 默认删除包含缺失值的行 % 如果只想删全空行,用以下方式 % T = T(all(~ismissing(T), 2), :); % 第二步:去除字符串两端的空格 strCols = varfun(@isstring, T, 'OutputFormat', 'uniform'); for col = find(strCols) T.(col) = strtrim(T.(col)); end % 第三步:把数值型文本转成数字 for col = 1:width(T) if iscell(T.(col)) || isstring(T.(col)) numericData = str2double(T.(col)); if all(~isnan(numericData)) % 能转就转 T.(col) = numericData; end end end

这个清洗思路的核心原则是:先加宽,再收紧。先把漏掉的数据补全、填缺失值,再做格式转换,最后才是删行删列。不要一上来就删数据,Excel里很多看似异常的数据,事后追溯时反而关键。

2.4 数据闭环:从Excel读出,处理好,再写回Excel

读数据只是第一步,分析完结果往往还要写回Excel。这里的关键是writetable和其参数设置。

% 把table写回Excel writetable(T, 'output.xlsx', 'Sheet', '结果'); % 如果不想覆盖原文件,写新Sheet writetable(T, 'output.xlsx', 'Sheet', '筛选结果'); % 数值格式可以逐个列指定 opts = detectImportOptions('output.xlsx'); % 自定义格式设置省略,根据具体需求选择 writetable(T, 'output_formatted.xlsx');

写回时有一个常见需求:把数值、字符串放在同一列时,Matlab默认可能把数值列写成科学计数法或截断。如果要精确控制格式,可以用xlswrite的低层写法或者直接生成Excel COM对象,但日常操作里,writetable配合对应参数已经能覆盖绝大多数场景。这里有个经验值:数据量小于1万行时,调用writetable的回写性能差异不大;超过1万行,建议分批写入并设置WriteMode为'append',否则很容易写了一半卡死。

3. 实战案例:从零搭建一个自动化的Excel导入管线

3.1 场景设定与需求拆解

结合我自己处理过的一个实际场景,把上面的技能串起来。这个任务是这样的:一个文件夹里每天会新增一堆Excel报表,每个报表里有多个Sheet,但Sheet结构和位置相对固定。需要写一个脚本,每次运行就能把当天所有文件读入、清洗、合并,然后输出一张汇总表。

拆解一下需求,无非是四件事:找文件、读文件、洗数据、写结果。难点在于文件数量多、Sheet名称并不完全一致、字段类型有波动。这四件事的思路是固定的,下面直接给可复制的代码。

3.2 完整代码实现

% 自动导入所有Excel报表并输出汇总 clear; clc; % 步骤1:配置路径与参数 inputFolder = 'D:\report_data\daily'; outputFile = 'D:\report_data\summary.xlsx'; filePattern = '*.xlsx'; % 步骤2:获取文件列表 files = dir(fullfile(inputFolder, filePattern)); if isempty(files) error('指定文件夹下没有找到Excel文件'); end % 步骤3:循环读取并清洗每个文件 allData = table(); for i = 1:length(files) filePath = fullfile(files(i).folder, files(i).name); % 读取主Sheet,这里用readtable自动识别类型 rawT = readtable(filePath, 'Sheet', 1); % 清洗:删除全空行、去除空格、转数值 rawT = rmmissing(rawT); for col = 1:width(rawT) if iscell(rawT.(col)) if ischar(rawT.(col){1}) || isstring(rawT.(col){1}) rawT.(col) = strtrim(string(rawT.(col))); numT = str2double(rawT.(col)); if all(~isnan(numT)) rawT.(col) = numT; end end end end % 记录文件来源 rawT.sourceFile = repmat(string(files(i).name), height(rawT), 1); % 合并到总表 allData = [allData; rawT]; %#ok<AGROW> end % 步骤4:输出汇总表 writetable(allData, outputFile, 'Sheet', '汇总'); fprintf('处理完成,共导入%d行数据\n', height(allData));

3.3 代码解析与关键决策

上面的代码里有两个地方值得展开说。

第一处是读取Sheet时用了'Sheet', 1,而不是Sheet名称。这里有个经验:当所有Excel的结构一致的时候,"取第一个Sheet"往往比"按名字找Sheet"更抗造。因为同事偶尔会改Sheet名,但是第一个Sheet的位置基本不会动。如果非要用Sheet名,可以先xlsfinfo确认一下,再决定读哪个。这个细节在实际项目中省了我不少修脚本的时间。

第二处是清洗逻辑里的ischar(rawT.(col){1})。这个判断是为了确认单元格里存的是字符类型,而不是数值或日期。加了这层判断,后面调strtrim才安全,避免把数值转成字符串再转回来,白白损失精度。

还有一点,变量allData = table()初始化为空表,后面循环里[allData; rawT]不断拼接。这种拼接方式虽然有效,但数据量大的时候效率低,官方文档里反复警告过。如果数据超过几十万行,更好的方式是先把每个文件的数据存到cell里,最后一次性合并。这里我用了拼接方式,因为这种应用场景数据量通常在几万行以内,影响不大。数据量大的场景建议改用dataList{i} = rawT,最后用cat(1, dataList{:})合并。

3.4 可变参数与容错机制的加入

真实环境里Excel文件往往比预想的不规范。我建议脚本里加一层容错:如果某个文件读取失败,不要中断整个流程,记录日志继续处理下一个文件。

% 容错读取方案 for i = 1:length(files) filePath = fullfile(files(i).folder, files(i).name); try rawT = readtable(filePath, 'Sheet', 1); % 处理... catch ME warning('文件%s读取失败:%s', files(i).name, ME.message); continue; end end

这层try-catch看着简单,实际使用价值很大。我有一次跑后台批量导入,发现第27个文件因为格式特殊导致整个脚本崩溃,前面26个文件全都白处理了。加了容错之后,即使有个别问题文件,其他文件照常处理,最后再针对性排查问题文件,效率提升不是一星半点。

4. 性能优化与大数据量处理策略

4.1 readtable的参数调优与检测选项

readtable有个重要参数是detectImportOptions生成的VariableImportOptions,它能精确控制每一列的数据类型。如果你事先知道某列是文本、某列是数值,直接指定类型,能大幅减少类型推断的计算开销。

% 预先指定导入选项,省去类型猜测 opts = detectImportOptions('large_data.xlsx'); opts.VariableTypes(3) = {'double'}; % 指定第3列是数值 opts.VariableTypes(5) = {'string'}; % 指定第5列是文本 T = readtable('large_data.xlsx', opts);

这里解释一下类型预判的原理:readtable在导入时需要扫描数据区域,推断每列的类型,这个扫描过程本身要消耗内存和时间。你指定了类型之后,Matlab就跳过了这一步,直接按指定映射读取,数据量越大,节省的时间越明显。实测百万行级别数据,这个优化能缩短约30%的读取时间。这不是数值计算出来的精确数值,是我在多次项目里观测到的体感区间。

4.2 大数据:datastore与tall数组玩法

单个Excel文件达到几百MB或者千万行时,直接用readtable读取极可能内存溢出。这时候有两个方案:分块读取和tall数组。

% 方案一:datastore分块读取,逐块处理 ds = datastore('huge_data.xlsx'); while hasdata(ds) chunk = read(ds); % 对chunk做处理,不要累积到内存里 end % 方案二:tall数组,把长数组交给后台处理 tt = tall(datastore('huge_data.xlsx')); summary(tt); % 在不加载全部数据的情况下做统计

tall数组的运作逻辑类似于延迟计算。你调用summary、mean、max这些聚合函数时,Matlab并不需要把全部数据载入内存,而是在后台逐块扫描、实时聚合。这个机制对付超大型Excel是最理想的。需要注意的是,tall数组不适合需要逐行访问和随机改值的场景,这种场景老老实实分块处理更合适。

4.3 避免内存溢出的常见优化习惯

几个我养成的习惯,按收益高低排序:

第一,按需指定Range,只读取实际需要的区域。很多人习惯把整张表读进来再删列,但Excel如果有一百万行,这种操作浪费的时间和内存都很大。读取时就指定Range,比读后裁剪更高效。

第二,用readmatrix读纯数值数据。readtable为了支持类型推断和列名处理,有一些额外开销。如果你是纯数值矩阵场景,用readmatrix更快更省内存。这是一个比较明显可感知的性能差异点。

第三,避免在循环里反复调用readtable。如果需要循环读取多个Sheet,可以先获取所有Sheet名,然后一次性读取多个Sheet,再拆分。readtable内部每次调用都有初始化成本,批量读取能摊薄这部分开销。

% 一次性读取多个Sheet,而不是在循环里反复调用 data = cell(1, length(sheetNames)); for i = 1:length(sheetNames) data{i} = readtable(filename, 'Sheet', sheetNames{i}); end % 这里的差异是逻辑层面的,readtable每次调用仍然有独立开销

4.4 深入底层:Excel COM操作的补充说明

如果你的Excel里有一些高级组件:数据透视表、图表、复杂公式、条件格式等,Matlab自带函数读写时可能丢这些元素。这时候可以考虑通过Excel COM接口操作,但这不是常规数据导入路径,只在必要时才碰。

% Excel COM操作(不推荐日常使用) e = actxserver('Excel.Application'); wb = e.Workbooks.Open('...路径...'); data = wb.Sheets.Item(1).UsedRange.Value; wb.Close(); e.Quit();

COM方案最大的优势是动作完整,能完整保留Excel文档里的几乎所有元素;最大的劣势是慢、容易残留进程。如果你COM操作后任务管理器里出现Excel进程无法关闭,通常是因为e.Quit()后面没有释放对象。所以COM方案是双刃剑,能用常规函数解决时绝不启动COM实例,毕竟谁也不想每次跑完脚本还要手动去任务管理器杀Excel进程。

5. 常见问题与排查技巧实录

5.1 路径与文件名相关的经典报错

最典型的报错是Unable to read file 'xxx.xlsx'.或者是文件找不到、路径错误。多数情况下是文件本身不存在,或者路径含有中文带空格。我给一个排查顺序:

% 检查文件是否真实存在 filePath = 'D:\data\实验 数据.xlsx'; fprintf('文件是否存在:%d\n', exist(filePath, 'file')); dir('D:\data\*.xlsx'); % 看看实际文件名是什么

一个很容易踩的坑是:Excel文件看似是.xlsx,实际上是个网页、XML或者旧版.xls改名过来的。这种文件Matlab无法解析。解决办法是用Excel打开另存为真正的.xlsx。还有一个场景:文件已经打开导致写入失败,需要将文件关闭。这里特别提醒:在Windows环境里,如果同一个Excel文件在进程里被占用,writetable写不进去会报权限错误。我的习惯是:脚本运行结束时检查临时文件是否占用,如果报了权限错误,第一时间去任务管理器看有没有残存的Excel进程。

5.2 中文乱码与编码问题

readtable读取Excel出现中文乱码的情况,通常不是标准Excel文件,而是CSV文件被改了后缀。读取CSV文件时需要指定编码格式,默认UTF-8常常和GBK源文件不兼容。

% 读取CSV时显式指定编码 T = readtable('data.csv', 'PreserveVariableNames', true); % 如果仍然乱码,考虑用fgetl+指定编码自行解析 fid = fopen('data.csv', 'r', 'n', 'GBK'); C = textscan(fid, '%s', 'Delimiter', ','); fclose(fid);

Excel本身存储的字符串基于Unicode内部格式,正则读出来基本都是正常显示中文。所以遇到乱码,第一反应应该是:这个文件是不是真Excel,还是CSV伪装的。

5.3 类型推断错误导致的数据错位

readtable自动推断类型偶尔会出错。最常见的是:一列大部分行是数字、但有少量行是文本(比如“NA”、“无数据”、“待补录”),readtable会直接把整列推断成文本cell,后续数值计算就全乱了。

% 类型混合时的对策 opts = detectImportOptions('data.xlsx'); opts.VariableTypes = {'double', 'string', 'categorical', 'datetime'}; T = readtable('data.xlsx', opts);

如果你预先知道这一列的绝大多数内容是数值,少量文本是异常值,直接用str2double处理后,非数值部分会变成NaN,倒入预设的double列,后续用ismissing识别即可。这个方法在我处理生产环境数据时反复用,比清洗文本再转数值要稳定得多。

5.4 数据量大的性能问题与处理建议

读取速度慢通常出现在文件大小超过50MB、行数超过几万行时。一个直接影响因素是Excel版本:xls格式比xlsx慢不少,xlsx又比xlsb慢。如果你频繁处理超大文件,可以和业务团队确认能否保存为xlsb格式——Matlab读取xlsb比读取xlsx更快。这个格式不一定适合所有人,在数据量极大时可以作为一个备选方案。

另一个性能因素:同时打开的Excel实例。如果你在脚本运行过程中手动开着Excel,Matlab读取同一个文件时会走Windows的文件锁机制,产生额外的等待和冲突。建议跑数据脚本时不要手动打开同一份文件。

5.5 问题排查速查表

症状可能原因排查方法
文件无法读取伪Excel、版本过旧、路径错误exist(filePath,'file')确认文件;用Excel另存为xlsx
中文乱码实际是CSV文件用fopen指定编码读取
数据错位/类型混乱列内类型混杂用detectImportOptions指定类型
写Excel失败文件被占用/无权限关闭Excel进程,用COM Quit或任务管理器清理
导入速度极慢文件超大、格式老旧用datastore分块、尝试xlsb格式
内存不足单次读入过大改用detectImportOptions+Range指定区域

5.6 调试技巧:部分读取帮大忙

有一个调试技巧的实用价值很高:排查大型Excel阅读问题时,先用Range参数只读前几十行,看看表格结构是否为预期,确认没问题后再完整读取。

% 先读前20行,确认结构 probe = readtable('data.xlsx', 'Range', 'A1:F20'); % 结构确认后再完整读 fullData = readtable('data.xlsx');

用探针式读取,能快速定位是文件本身结构问题,还是导入参数设置问题,省去反复跑全量读取的时间。另外,获取表格行数不一定要全读进来,一些基础信息可用xlsfinfo间接获取,这在一开始整理文件概览时很有用。

6. 写在最后的实践经验分享

Matlab读取Excel这件事,说到底是个典型的“门槛低、深度高”需求。每次都靠搜索引擎现查、粘贴一个旧脚本、改改路径继续用,短期看省事,长期看维护成本极高。如果你打算长期和数据处理打交道,花一下午把readtable、detectImportOptions、datastore这套现代函数体系用熟,是值得的。

我个人的一个小体会:读完数据不是结束,搞清楚数据长什么样才重要。所以每次读取后,花10秒钟看一眼summary(T)和head(T),能省掉后面大量的debug时间。

T = readtable('data.xlsx'); summary(T); % 查看每一列的类型、缺失值、极值 head(T); % 看前几行,确认数据拼装是对的

还有一个我反复踩坑后的习惯是:尽量不要在脚本里硬编码文件的绝对路径。把输入输出路径整理到脚本开头,用fullfile拼接,路径变更的时候只需要改一处。这个习惯算是一个朴素的工程化经验,但用下来的收益非常大——你永远不会想在一个几百行脚本里找那个写死的路径字符串的。

最后给一个小技巧:如果项目里反复用到“读取某个结构的Excel并清洗”这个流程,建议把它封装成函数,放在一个公共工具包里。下次遇到类似需求,一行调用就完了,不用再复制几十行代码。这个封装的过程,就是你从“会调函数”走向“会写工具”的起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询