在Matlab中读取和处理大型文本文件

4

我正在尝试将一个很大的文本文件(几百万行)读入Matlab。最初我使用了importdata(file_name)这个函数,它看起来是一个简洁的解决方案。然而,我需要使用Matlab 7(是的,我知道它很老),而且似乎不支持importdata。因此,我尝试了以下方法:

while ~feof(fid)    
    fline = fgetl(fid);
    fdata{1,lno} =  fline ;
    lno = lno + 1;
end

但是这样做非常慢。我猜测这是因为它在每次迭代时都调整数组大小。有没有更好的方法来做到这一点。请注意,输入数据的前20行是字符串类型数据,其余数据是3到6列十六进制值。

3个回答

5

您需要进行一些重塑,但另一个选项是您可以使用fread。但正如提到的那样,这基本上将您锁定为矩形导入。因此,另一个选择是使用textscan。正如我在另一个注释中提到的那样,我不确定它是何时实现的,我只知道您没有“importdata()”。

fid = fopen('textfile.txt')
Out  = textscan(fid,'%s','delimiter',sprintf('\n'));
fclose(fid)

使用textscan,您将能够为每行获取一个字符的单元格数组,然后可以随意操纵它们。正如我在评论中所说,这不再关心行是否具有相同的长度。现在,您可以更快地解析单元格数组。但是,正如gnovice提到的,并且他也有一个非常优雅的解决方案,您可能需要考虑内存需求。
如果可以避免使用循环结构,则永远不要在matlab中使用它们。它们在C/C++等语言中速度很快,但在matlab中,它们是最慢的方法。
编辑:刚刚查了一下,看起来textscan确实是在版本7(R14)中实现的,因此如果您有该版本,应该可以使用它。

哦,现在你只是在测试我的技能 :) 我已经编辑了我的帖子,以反映一种方法,可以让你避免行长度相同的问题。 - dynamphorous
TEXTSCAN 很好用。我不确定在版本 7 中是否可用。我的基于 FSCANF 的解决方案基本上也可以实现相同的功能,但需要打更多的字 :)。 - gnovice
是的,这就是为什么我为你的解答点赞。它是一个非常好的解决方案,利用了很多可能缺失的函数的变通方法。当Matlab没有相应功能的函数时,使用for或while循环几乎从来不是一个好的起点!我宁愿编写自己的MEX,也不想每次都要忍受数十亿次的for循环迭代 :) - dynamphorous

2
我看到两个选项:
  1. 不要每次都增加1,只有在必要时才将数组的大小加倍。这样可以大大减少需要重新分配的次数。
  2. 采用两步方法。第一步仅计算行数,而不存储它们。第二步实际填充数组(已预先分配到正确的大小)。

听起来很合理 - 我该怎么做呢?因为我对使用{}单元格与普通数组有些困惑。 - trican
@trican:每次lno达到2的幂时(例如),都将fdata与一个1xlno个空矩阵单元数组连接起来,即fdata = [fdata cell(1,lno)] - Oliver Charlesworth
您还可以通过将元素分配到末尾之外来扩展它:fdata(end*2) = fdata(1);。适用于大多数数据类型。 - Andrew Janke

2
一种解决方案是使用 FSCANF 将文件的整个内容作为字符字符串读取,使用 MAT2CELL 在换行符出现的位置将字符串分割成单独的单元格,使用 STRTRIM 去除端部多余的空格,然后根据需要处理每个单元格中的字符串数据。例如,使用此示例文本文件 'junk.txt'
hi
hello
1 2 3
FF 00 FF
12 A6 22 20 20 20
FF FF FF

以下代码将把每一行放入一个单元数组cellData的单元格中:
>> fid = fopen('junk.txt','r');
>> strData = fscanf(fid,'%c');
>> fclose(fid);
>> nCharPerLine = diff([0 find(strData == char(10)) numel(strData)]);
>> cellData = strtrim(mat2cell(strData,1,nCharPerLine))

cellData = 

    'hi'    'hello'    '1 2 3'    'FF 00 FF'    '12 A6 22 20 20 20'    'FF FF FF'

现在,如果你想将所有十六进制数据(样本数据文件中的第3到6行)从字符串转换为数字向量,可以使用CELLFUNSSCANF,代码如下:
>> cellData(3:end) = cellfun(@(s) {sscanf(s,'%x',[1 inf])},cellData(3:end));
>> cellData{3:end}    %# Display contents

ans =

     1     2     3

ans =

   255     0   255

ans =

    18   166    34    32    32    32

ans =

   255   255   255

注意:由于你正在处理如此大的数组,你必须注意变量使用的内存量。上述解决方案是矢量化的,但可能会占用大量内存。当你创建cellData时,你可能需要覆盖或清除strData这样的大变量。另外,你可以循环遍历nCharPerLine中的元素,并逐个处理更大的字符串strData的每个段落,将其转换为所需的向量,这样你就可以预先分配现在已知有多少行数据(即nDataLines = numel(nCharPerLine)-nHeaderLines;)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接