QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1038|回复: 0
打印 上一主题 下一主题

MATLAB中获取大型文本文件行数方法研究

[复制链接]
字体大小: 正常 放大

2620

主题

162

听众

1万

积分

升级  0%

  • TA的每日心情
    开心
    2015-3-12 15:35
  • 签到天数: 207 天

    [LV.7]常住居民III

    社区QQ达人 发帖功臣 新人进步奖 优秀斑竹奖 金点子奖 原创写作奖 最具活力勋章 助人为乐奖 风雨历程奖

    群组第六届国赛赛前冲刺培

    群组国赛讨论

    群组2014美赛讨论

    群组2014研究生数学建模竞

    群组数学中国试看培训视频

    跳转到指定楼层
    1#
    发表于 2014-10-20 10:33 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    在工作中会有很多特殊的需要,比如我现在就遇到一个需要将大型的文本格式数据文件(比如5G)读取到MATLAB中,同时进行一定的处理。由于XP的内存是绝对没有办法将5G的数据一次性加载到工作空间的,此时一般我们是对数据进行分段处理,为了防止长时间等待,让用户以为是死机了,这个时候我们希望添加一个进度条告诉用户处理到什么地方了。但是我们必须先统计文件的行数,才能给出进度。

    另外有些用户希望获取文件的长度(字符个数),哦这个其实很简单!
    fid=fopen('data.csv');
    fseek(fid,0,'eof')
    filelength = ftell(fid);
    fclose(fid);
    复制代码
    现在问题来了,如何获取文本文件的行数呢??下面我们提供几种解决方案吧!本文代码中测试的data.csv数据文件总共有10万行,大概77M。

    方法一

    最容易想到的就是使用MATLAB的fgetl或fgets函数,对文本按行读取,然后对行数进行累加。
    tic
    fid=fopen('data.csv'); % 打开文件
    row=0;
    while ~feof(fid) % 是否读取到文件结尾
        [~]=fgets(fid); % 或者fgetl
        row=row+1; % 行数累加
    end
    fclose(fid); % 及时关闭文件是个好习惯
    row
    toc
    复制代码
    运行结果如下,fgets大概耗时大概10s,如果fgetl速度会慢一些,大概需要13s,可能是因为fgel需要将尾部的回车去掉而增加了操作吧
    row =

          100000

    Elapsed time is 10.556020 seconds.

    方法二

    其实MATLAB处理复杂文本文件,灵活性最好和效率最高的是textscan函数。灵活性就不用说了,textscan提供很多定制功能,比如跳过的标题行数、设置空白字符等经过测试;至于效率,经过测试,textscan在处理某些数据下是效率fscanf的10倍以上,另外仔细看看dlmread函数,其实也是调用了textscan函数。

    接着有人要问,textscan是用来读取数据的,怎么用来统计行数?其实我们只是占了一个便宜而已!因为textscan提供了一个忽略特定字符串的功能。
    tic
    fid=fopen('data.csv');
    % '%1c%*[^\n]' 这个设置估计有人看不懂,特别是后面的那个%
    % %1c,表示读取一个字符
    % %*[^\n],*表示忽略,[^]表示不是[]字符,合起来的意思就是忽略所有不是\n(回车)的字符,更直接的意思就是忽略到行尾
    data = (textscan(fid,'%1c%*[^\n]'));
    fclose(fid);
    row=length(data{1})
    toc
    复制代码
    row =

          100000

    Elapsed time is 12.660186 seconds.

    运行时间12s,好像有点长哦,没有想象中的那么厉害。另外这个方法有两个个致命的弱点,因为程序必须每行读取一个字符
    (1)假如文件很大,比如10G,那么就算每行读取一个字符,这个也超出了XP内存,因此读取失败!
    (2)假如存在空行,那么%1c会将回车读入,%*[^\n]于是就自动忽略了下一行,因此统计的行数不准确!

    从上面分析textscan并不适合用于统计大型文件的行数,但是这并不能否定textscan的效率,因为textscan是千真万确一行一行的处理和读取数据文件,只是我们读取第一个字符然后忽略了剩下的所有字符而已。


    方法三

    其实越是底层的函数效率是越高的,只是使用不方便而已。MATLAB还有一个fread函数,不过这是默认处理的是二进制文件,不过没有关系,文本文件只是一个编码而已,我们还是可以使用fread进行读取的。
    tic
    fid=fopen('data.csv','rt'); % t是告诉fread是这里文本文件
    row=0;
    while ~feof(fid)
        % 一次性读取10000字符,计算其中的回车个数,其中10是回车的ASCII编码
        % '*char'表示每次读取一个字符,*表示输出也是字符
        % 放心fread现在已经可以自动识别中文了,万一还是识别不了,
        % 请在fopen中指定文件编码格式,比如gbk
        row=row+sum(fread(fid,10000,'*char')==char(10));
        % 下面还有一个类似的方法,但是效率低很多,大概是上面的一半
        % 'char'表示每次读取一个字符,但是默认输出double,
        % 也就是说读取char然后转换double中间有转换能快吗?
        % row=row+sum(fread(fid,10000,'char')==10);
    end
    fclose(fid);
    row
    toc
    复制代码
    这个效率呱呱的,简直天壤之别呀,才1.7s!看来这个结果比较令我满意哦!
    row =

          100000

    Elapsed time is 1.721880 seconds.

    方法四

    上面的方法都是在循环中不停地对文件进行访问,自然效率是高不起来的。对于大型文件,还有其他什么好的解决方案呢,也许这个时候需要借组外部力量了!传说perl语言对文件操作有很多优势,同时linux提供了wc命令对文件进行行统计,不妨试试?
    tic
    % 判断计算机操作系统
    if (isunix) % Linux系统提供了wc命令可以直接使用
        % 使用syetem函数可以执行操作系统的函数
        % 比如window中dir,linux中ls等
        [~, numstr] = system( ['wc -l ', 'data.csv'] );
        row=str2double(numstr);
    elseif (ispc) % Windows系统可以使用perl命令
        if exist('countlines.pl','file')~=2
            % perl文件内容很简单就两行
            % while (<>) {};
            % print $.,"\n";
            fid=fopen('countlines.pl','w');
            fprintf(fid,'%s\n%s','while (<>) {};','print $.,"\n";');
            fclose(fid);
        end
        % 执行perl脚本
        row=str2double( perl('countlines.pl', 'data.csv') );
    end
    row
    toc
    复制代码
    楼主使用的是window系统,调用perl,果然不负众望,才0.89秒
    row =

          100000

    Elapsed time is 0.889994 seconds.




    其实上面的方法在处理真正的大型文件时,还是可能不是很理想的,本文中测试的文件才77M算不上什么大型文件,后来对一个大约80万行,大小622M的csv文件进行测试,使用perl方法结果如下:
    row =

          800001

    Elapsed time is 15.859564 seconds.

    文件越大,计算时间不是简单的线性增加!到了真正几十甚至几百G这样的大型数据,上面的方法几乎是不能忍受的,也许还有更好的解决方法吧!

    有人问有什么好的方法生成上面的测试数据呀?我在MATLAB中是这样生成的!
    data=rand(10000,100); % 随机生成1w行数据
    save data.csv data -ascii % 保存为文本
    for ii=1:5 % 自己复制5次,生成2^5=32万行
    % 这里使用了dos命令,效率会好些
    % 千万不要使用fprint,否你会残废的
       !type data.csv >> data.csv
    end
    复制代码

    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2025-5-22 14:43 , Processed in 0.585903 second(s), 54 queries .

    回顶部