如何快速格式化读取大文件

忘世麒麟 2016-10-20 05:25:33
比如有一个文件,每一行的格式都是固定的,但是有几十万行,例如部分举例内容如下(使用tab键分割的字段):

ZXZ1 ZXZ1 ICE NULL IC cmd 纽元/美元11 69
ZXZ2 ZXZ2 ICE NULL IC cmd 纽元/美元12 69
ZXZ3 ZXZ3 ICE NULL IC cmd 纽元/美元13 69
ZXZ4 ZXZ4 ICE NULL IC cmd 纽元/美元14 69
ZXZ5 ZXZ5 ICE NULL IC cmd 纽元/美元15 69
ZXZ6 ZXZ6 ICE NULL IC cmd 纽元/美元16 69

程序需要读取这些内容到内存中,我用一个结构体对象代表每一行的不同字段
比如我用的结构体类似这样:

struct node
{
CString Acode; //ZXZ6
CString Bcode; //ZXZ6
CString Ccode; //ICE
CString Dcode; //NULL
CString Ecode; //IC
CString Fcode; //cmd
CString Gcode; //纽元/美元16
int Hcode; //69
};


现在已有的读取程序逻辑是这样的:
打开文件后,一次读取一行,然后通过switch-case语句去生成结构体对象,最后将完整的一个对象push_back到一个vector中去
代码片段如下:

if( file.Open( pszFile, CFile::modeRead | CFile::shareDenyWrite ) )
{
while( file.ReadString( strData ) )
{
strField = strData.Tokenize("\t", iStart );
while( strField.GetLength() )
{
switch( index )
{
case 0:
symbolData.Acode = strField;
break;
case 1:
symbolData.Bcode = strField;
break;
case 2:
symbolData.Ccode = strField;
break;
case 3:
symbolData.Dcode = strField;
break;
case 4:
symbolData.Ecode = strField;
break;
case 5:
symbolData.Fcode = strField;
break;
case 6:
symbolData.Gcode = strField;
break;
case 7:
symbolData.Hcode = atoi( strField );
break;
}
++index;
strField = strData.Tokenize("\t", iStart );
}
m_vctSymbolDataArr.push_back( symbolData );
......
}
file.Close();
}

这段代码最大的问题在于,当行数达到几十万条数百万调的时候,执行很慢.小数据量(20M/30M等)还行.
请问:有没有其他办法可以快速的读取此类文件
...全文
653 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
振翅高飞 2016-10-25
  • 打赏
  • 举报
回复
多线程分段读取,将大文件切割。所谓切割就是每一个线程读取大文件的一部分。累加若干线程的数据就是一个完整的大文件数据。 比如3G的文件,切分16个线程,一个线程分担(3*1024*1024*1024Byte/线程数 )个数据量 注意线程同步时数据写入同一个容器时需要加锁。
忘世麒麟 2016-10-25
  • 打赏
  • 举报
回复
引用 21 楼 gz_qmc 的回复:
首先,重复字串那么多,这个全用字串来存储数据,原本是一个2B行为 第二,既然已经这样了,数据并非要全读, 你全读取,你用得到那么多吗? 但是又需要用的时候快速获取,那么建立索引文件可以吗? 每条记录在源文件的偏移用long 4字节 100万条记录就需要1000000*4=4000000=3906.25K=3.82M 用4M吧, 4M!!!!喜欢就用文件映射,不喜欢就直接开内存
第一:有重复?本身就是举的一个例子,我不会把真是的数据贴出来吗? 第二:我觉得我在一楼写的那些字没有体现"不需要全部读取"这个意思吧?而且这段代码的使用场景我没写,索引并不适合.
赵4老师 2016-10-25
  • 打赏
  • 举报
回复
在文件大小相同的前提下: 读刚读过的文件比头次读没读过的文件快 读转速快的硬盘上的文件比读转速慢的硬盘上的文件快 读没有磁盘碎片的文件比读有磁盘碎片的文件快 读文件不处理比边读边处理快 单线程从头到尾一次读文件比多线程分别读文件各部分快(非固态硬盘上) 读固态硬盘上的文件比读普通硬盘上的文件快
忘世麒麟 2016-10-24
  • 打赏
  • 举报
回复
引用 13 楼 baijiaheizhiganmao 的回复:
[quote=引用 12 楼 a7716021 的回复:] @赵4老师 comeon
他会复制一段话:程序关键是自己动手,...... 而且楼上的方法也不错哦[/quote] CString 这种类型在频繁拷贝复制的时候很耗时.老代码,看得我头疼,改成内置的数据类型,发现要改的东西实在太多.前人真是人才!
gz_qmc 2016-10-24
  • 打赏
  • 举报
回复
首先,重复字串那么多,这个全用字串来存储数据,原本是一个2B行为 第二,既然已经这样了,数据并非要全读, 你全读取,你用得到那么多吗? 但是又需要用的时候快速获取,那么建立索引文件可以吗? 每条记录在源文件的偏移用long 4字节 100万条记录就需要1000000*4=4000000=3906.25K=3.82M 用4M吧, 4M!!!!喜欢就用文件映射,不喜欢就直接开内存
鼎劍閣 2016-10-22
  • 打赏
  • 举报
回复
多线程同时处理,vector使用时候注意一下,会不断的复制内存
赵4老师 2016-10-21
  • 打赏
  • 举报
回复
引用 13 楼 baijiaheizhiganmao 的回复:
[quote=引用 12 楼 a7716021 的回复:] @赵4老师 comeon
他会复制一段话:程序关键是自己动手,...... 而且楼上的方法也不错哦[/quote] 程序关键是参考zhao4zhong1的代码自己动手,......
paschen 版主 2016-10-21
  • 打赏
  • 举报
回复
内存映射不会明显增加效率,只是可以像读写内存那样方便地读写文件
赵4老师 2016-10-21
  • 打赏
  • 举报
回复
再供参考:
//输出PROG中有但LIST中没有的文本行,即集合PROG-LIST
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <search.h>
#define MAXCHARS 512
int MAXLINES=10000,MAXLINES2;
char *buf,*buf2;
char PROG[256]="PROG";//程序Program需要的文件列表
char LIST[256]="LIST";//dir /b /s生成的实际文件列表List
FILE *fp,*fl;
int i,c,n,L,hh;
int ignore_case=0;
char ln[MAXCHARS];
int icompare(const void *arg1,const void *arg2) {
   return stricmp((char *)arg1,(char *)arg2);
}
int compare(const void *arg1,const void *arg2) {
   return strcmp((char *)arg1,(char *)arg2);
}
int main(int argc,char **argv) {
    if (argc>1) strcpy(PROG,argv[1]);//命令行参数1覆盖PROG
    if (argc>2) strcpy(LIST,argv[2]);//命令行参数2覆盖LIST
    if (argc>3) ignore_case=1;//若存在命令行参数3,忽略大小写
    if ((fl=fopen(LIST,"rt"))==NULL) {
        fprintf(stderr,"Can not open %s\n",LIST);
        fprintf(stderr,"Usage: %s [PROG] [LIST] [-i]\n",argv[0]);
        return 1;
    }
    if ((fp=fopen(PROG,"rt"))==NULL) {
        fclose(fl);
        fprintf(stderr,"Can not open %s\n",PROG);
        fprintf(stderr,"Usage: %s [PROG] [LIST] [-i]\n",argv[0]);
        return 2;
    }
    buf=(char *)malloc(MAXLINES*MAXCHARS);
    if (NULL==buf) {
        fclose(fl);
        fclose(fp);
        fprintf(stderr,"Can not malloc(%d LINES*%d CHARS)!\n",MAXLINES,MAXCHARS);
        return 4;
    }
    n=0;
    hh=0;
    i=0;
    while (1) {
        if (fgets(ln,MAXCHARS,fl)==NULL) break;//
        hh++;
        L=strlen(ln)-1;
        if ('\n'!=ln[L]) {//超长行忽略后面内容
            fprintf(stderr,"%s Line %d too long(>%d),spilth ignored.\n",LIST,hh,MAXCHARS);
            while (1) {
                c=fgetc(fl);
                if ('\n'==c || EOF==c) break;//
            }
        }
        while (1) {//去掉行尾的'\n'和空格
            if ('\n'==ln[L] || ' '==ln[L]) {
                ln[L]=0;
                L--;
                if (L<0) break;//
            } else break;//
        }
        if (L>=0) {
            strcpy(buf+i,ln);i+=MAXCHARS;
            n++;
            if (n>=MAXLINES) {
                MAXLINES2=MAXLINES*2;
                if (MAXLINES2==1280000) MAXLINES2=2500000;
                buf2=(char *)realloc(buf,MAXLINES2*MAXCHARS);
                if (NULL==buf2) {
                    free(buf);
                    fclose(fl);
                    fclose(fp);
                    fprintf(stderr,"Can not malloc(%d LINES*%d CHARS)!\n",MAXLINES2,MAXCHARS);
                    return 5;
                }
                buf=buf2;
                MAXLINES=MAXLINES2;
            }
        }
    }
    fclose(fl);
    if (ignore_case) qsort(buf,n,MAXCHARS,icompare);
    else qsort(buf,n,MAXCHARS,compare);
    hh=0;
    while (1) {
        if (fgets(ln,MAXCHARS,fp)==NULL) break;//
        hh++;
        L=strlen(ln)-1;
        if ('\n'!=ln[L]) {//超长行忽略后面内容
            fprintf(stderr,"%s Line %d too long(>%d),spilth ignored.\n",PROG,hh,MAXCHARS);
            while (1) {
                c=fgetc(fp);
                if ('\n'==c || EOF==c) break;//
            }
        }
        while (1) {//去掉行尾的'\n'和空格
            if ('\n'==ln[L] || ' '==ln[L]) {
                ln[L]=0;
                L--;
                if (L<0) break;//
            } else break;//
        }
        if (L>=0) {
            if (ignore_case) {
                if (NULL==bsearch(ln,buf,n,MAXCHARS,icompare)) printf("%s\n",ln);
            } else {
                if (NULL==bsearch(ln,buf,n,MAXCHARS,compare)) printf("%s\n",ln);
            }
        }
    }
    fclose(fp);
    free(buf);
    return 0;
}
赵4老师 2016-10-21
  • 打赏
  • 举报
回复
引用 12 楼 a7716021 的回复:
@赵4老师 comeon
CString是对象,不是一段内存,不适合放在结构体中。 仅供参考:
//NAME: essaie bla bla
//DIMENSION: 8
//DATA
//1  14  15
//2  11  10
//3  6   4
//4  7   13
//5  9   21
//6  19  3
//7  1   5
//8  8   8
//EOF
//
// 文本文件中可能还含有其他内容,但是需要用到的内容即以上

//比如data.txt:
//NAME: essaie bla bla
//其它内容
//DIMENSION: 8
//其它内容
//DATA
//其它内容
//1  14  15
//其它内容
//2  11  10
//其它内容
//3  6   4
//其它内容
//4  7   13
//其它内容
//5  9   21
//其它内容
//6  19  3
//其它内容
//7  1   5
//其它内容
//8  8   8
//其它内容
//EOF

// 目标是要获取NAME后字串,DIMENSION后数值,以及DATA以下的数值
// 其中NAME就是随便个字句,DIMENSION是城市数量,DATA以下是城市编号,X坐标,Y坐标
// 所有的这些将赋值给一个事先定义好的结构
#include <stdio.h>
#include <string.h>
#define MAXCPL   80   //每行最大字符数
#define MAXCITY  100  //每组数据中DATA最多项数,DIMENSION的最大值
#define MAXNAMEL 32   //NAME最大长度
struct S {
    char NAME[MAXNAMEL+1];
    int  DIMENSION;
    struct D {
        int NO;
        int X;
        int Y;
    } DATA[MAXCITY];
} s;
FILE *f;
int st,n,i;
char ln[MAXCPL];
int main() {
    f=fopen("data.txt","r");
    if (NULL==f) {
        printf("Can not open file data.txt!\n");
        return 1;
    }
    st=0;
    n=0;
    while (1) {
        if (NULL==fgets(ln,MAXCPL,f)) break;
        if (st==0) {
            if (1==sscanf(ln,"NAME: %31[^\n]",s.NAME)) st=1;
        } else if (st==1) {
            if (1==sscanf(ln,"DIMENSION: %d",&s.DIMENSION)) st=2;
        } else if (st==2) {
            if (0==strcmp(ln,"DATA\n")) st=3;
        } else if (st==3) {
            if (3==sscanf(ln,"%d%d%d",&s.DATA[n].NO,&s.DATA[n].X,&s.DATA[n].Y)) {
                n++;
                if (n>=MAXCITY || n>=s.DIMENSION) break;
            }
        }
    }
    fclose(f);
    printf("s.NAME=[%s]\n",s.NAME);
    printf("s.DIMENSION=%d\n",s.DIMENSION);
    for (i=0;i<n;i++) {
        printf("s.DATA[%d].NO,X,Y=%d,%d,%d\n",i,s.DATA[i].NO,s.DATA[i].X,s.DATA[i].Y);
    }
    return 0;
}
//s.NAME=[essaie bla bla]
//s.DIMENSION=8
//s.DATA[0].NO,X,Y=1,14,15
//s.DATA[1].NO,X,Y=2,11,10
//s.DATA[2].NO,X,Y=3,6,4
//s.DATA[3].NO,X,Y=4,7,13
//s.DATA[4].NO,X,Y=5,9,21
//s.DATA[5].NO,X,Y=6,19,3
//s.DATA[6].NO,X,Y=7,1,5
//s.DATA[7].NO,X,Y=8,8,8

line_us 2016-10-21
  • 打赏
  • 举报
回复
好复杂,头皮发麻
忘世麒麟 2016-10-21
  • 打赏
  • 举报
回复
引用 12 楼 a7716021 的回复:
@赵4老师 comeon
他会复制一段话:程序关键是自己动手,...... 而且楼上的方法也不错哦
a7716021 2016-10-21
  • 打赏
  • 举报
回复
@赵4老师 comeon
忘世麒麟 2016-10-21
  • 打赏
  • 举报
回复
引用 4 楼 paschen 的回复:
文件格式是固定的直接移动文件指针到要读取的地方直接读取要读的数据就可以了
引用 5 楼 lianshaohua 的回复:
内存映射,分段处理,处理一段写一段。
引用 7 楼 u010611736 的回复:
如果可以,减少文件大小,改成二进制。 如果其他原因不能改文件结构,可以用楼上各位说的。 然后就是固定读:fscanf。 和scanf,sscanf相似的。
内存映射和和fscanf是个不错的方法,但是,直接移动文件指针在我的项目中不太合适(场景不适用).我觉得不要构造对象,直接在vector中保存指针,这样可以减少很多次的拷贝构造和析构,这也许不失为另外一个方法.
忘世麒麟 2016-10-21
  • 打赏
  • 举报
回复
引用 2 楼 bjym1987 的回复:
内存映射会不会快点
这也许是个好主意,我会试一试
忘世麒麟 2016-10-21
  • 打赏
  • 举报
回复
引用 1 楼 CGabriel 的回复:
如果你不在乎内存消耗,可以把整个文件读入内存先: http://cpp.indi.frih.net/blog/2014/09/how-to-read-an-entire-file-into-memory-in-cpp/ 然后慢慢折腾,速度会快好多。。
内存是必须注意的,不然电脑就卡死了
hongwenjun 2016-10-20
  • 打赏
  • 举报
回复
你可以参考下面的代码,读取整行,先判断是否是注解行,然后使用 strtok(line, " ,:;-"); 使用 strtok切割,这样处理文本是比较高效的
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;

typedef  char*  c_string ;
int loadfile_strid(FILE* in , c_string* ps_strid , int max);

int main()
{
    char InstrumentID[7][32];
    c_string ppInstrumentID[7];
    for (int i = 0; i != 7 ; ++i) {
        ppInstrumentID[i] = InstrumentID[i];
    }

    // 下面来实现从文件中读取配置
    FILE* in = fopen("strid.txt" , "r");

    int readflag = loadfile_strid(in , ppInstrumentID , 7);
    if (readflag == 7) {
        cout << "这行有数据" << endl;
    }
    readflag = loadfile_strid(in , ppInstrumentID , 7);
    if (readflag == -1)
        cout << "这行注解" << endl;

    readflag = loadfile_strid(in , ppInstrumentID , 5);
    if (readflag > 0) {
        c_string* it = ppInstrumentID;     // auto 自动判别类型 char**
        c_string* end = ppInstrumentID + 5;
        while (it != end)    // 迭代器遍历输出
            cout << *it++ << endl;
    }

    fclose(in);
    return 0;
}

// 从配置文件 in 里读取数据,存放到c_string* ps_strid 指向空间,max是一行中数据数量
int loadfile_strid(FILE* in , c_string* ps_strid , int max)
{
    // 读取一行
    char line[512] = {0};
    fgets(line, 512, in);
    if (strstr(line, "#"))
        return -1; // 注解行 返回 -1
    int index = 0;
    char* pch;

    // 切割后,然后复制到指针指向分配的空间的地址
    pch = strtok(line, " ,:;-");
    while (pch != NULL && max--) {
        strcpy(ps_strid[index] , pch);
        pch = strtok(NULL, " ,:;-");
        index++;
    }

    return index;  // 没有切割返回0  , 可以判断切换后数量是否正确
}


/*   filename:  strid.txt     分割符号 " ,:;-"

   Panzer ,   蘭公子二世  : 秦.狮王  ;  似水無痕 -  kerou   H0rol4   GM
#  注解行  ;  似水無痕 -  kerou   H0rol4   GM  Panzer ,   蘭公子二世  :
  秦.狮王  ;  似水無痕 -  kerou   H0rol4   GM  Panzer ,   蘭公子二世  :

*/
walkereklaw 2016-10-20
  • 打赏
  • 举报
回复
如果可以,减少文件大小,改成二进制。 如果其他原因不能改文件结构,可以用楼上各位说的。 然后就是固定读:fscanf。 和scanf,sscanf相似的。
eastfriendwu 2016-10-20
  • 打赏
  • 举报
回复
可能这段程序有几个优化点: 1)读取文件系统调用太频繁,建议使用类似mmap的内存映射 2)vector容器内存分配太频繁,能不能考虑vector的reserve() 3 ) 能不能优化掉switch,把struct node 的成员改成 字符串数组 4)貌似字符串Cstring拷贝太多次了。
ztenv 版主 2016-10-20
  • 打赏
  • 举报
回复
内存映射,分段处理,处理一段写一段。
加载更多回复(4)

64,644

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧