我用 CStdioFile 来打开文件,一次性把文件的内容读到内存中,然后对内存中的字符串进行处理。原txt文件里面,是按行存储一些中文、英文字符,每行都有一个以上的 ‘|’ 字符,一共有30多万行。我想按照所在行的内容里面的 ‘|’字符个数来排序,也就是,把所有行中,每行只有一个 '|' 字符的,排在前面,每行有2个的,排在其次,其它的以此类推。有点像“畸形”的金字塔,少的在上面,多的在下面,堆积起来。我在VC 里面的做法是,把前面读取进来的(以文本方式读取进来)字符串,以换行符 CRLF 为标志,逐行读取到一个临时字符串 tem 中,然后,判断这个临时字符串tem 里面有 n 个 '|'字符,然后把它存储到专门存储 n 个 ‘|’字符的字符串里,当把内存中的30多万行都读取完后,也就分析完毕了,然后,依次 把存储一个‘|’字符、存储两个‘|’字符、存储三个 ‘|’字符、、、的字符串内容写入到一个新的 txt文件里面。但是,发现这样做,速度很慢,我试过不用线程、用单线程、用多线程来分析,效果都一样,非常慢。(如果要分析的 txt 内容比较小,比如几百KB,当然没问题)。各位大侠,应该用什么方法来实现我的这个功能呢? 谢谢指点!

解决方案 »

  1.   

    有多慢?慢在哪里?是不是计算太多了,的确需要这么多CPU时间.
      

  2.   


    我那个txt文件一共才 20多兆。。逻辑上我也不晓得哪里出错了算法方面,就是对每一行进行遍历,然后统计 ‘|’ 出现的次数,根据这个次数,把这一行添加到相应的字符串空间中。其他的,实在想不出哪里错了
      

  3.   

    不要新建字符串,试试这个,保存指针就行了
    代码在记事本里写的,你自己测试下vector<vector<char*>> _line_arr;_line_arr.reserve(10); //假设最多一行有10个'|'int cnt = 0;
    char* pstart = data; //data是你读取出来的数据,filelen是数据长度
    for(int i = 0;i < filelen;)
    {
        if(data[i] == '\r') //回车;
    {
    _line_arr[cnt-1].pushback(pstart); //把这一行的指针放到_line_arr数组里去
    data[i] = 0; //把换行符变成0,这样pstart指向的就是一个字符串

    i += 2;
    pstart = data + i; //下一行 cnt == 0;
    continue;
    }
    else if(data[i] == '|')
    ++cnt;
    ++i;
    }//遍历vector写文件
      

  4.   

    _line_arr.reserve(10); 改成 _line_arr.resize(10);
      

  5.   


    没进入死循环,因为我测试过,差不多一个小时,才能把那个 txt 文本排序完成,写入新的文本中,如果进入死循环,是不可能完成的。
      

  6.   

    CStdioFile的效率极其低下!直接用映射文件扫描,我处理100万行100多兆也就几十秒,系统缓冲以后第二次读只需2秒。
      

  7.   

    内存映射俺可不会,俺是怎么熟怎么做,本质上都是从硬盘上读数据,在速度上不应该有质的差别,俺是只会用fopen、fread这些函数。
      

  8.   

    void CMyDlg::MyProc(CString filepath)
    {
    //---------------------------------------------------------
    //BeginWaitCursor();
    //===========================================================
    CString strtem="",te="";
    CStringList *list2=new CStringList,*list3=new CStringList,*list4=new CStringList,*list5=new CStringList,*list6=new CStringList,*list7=new CStringList,*list8=new CStringList;
    int num=0,index=0;
    int fileindex=0,totalnum=0;
    //========================================================================
    char *ttt=NULL;
    CString strread;
    BOOL bRet=FALSE;
    CStdioFile filein,fileout; if (filein.Open(filepath,CFile::modeReadWrite | CFile::typeText))
    {
    do{
    bRet = filein.ReadString(strread); //一次读取一行
    if (strread.IsEmpty())
    {
    break;
    }
    // 串处理 .Find; .TrimLeft .TrimRight .Left
    ttt=(LPSTR)(LPCTSTR)strread;
    for (int k=0;k<strread.GetLength();k++)
    {
    if (ttt[k]=='|')
    {
    num++;
    }
    } switch (num)
    {
    case 1:
    list2->AddTail(strread);
    break;
    case 2:
    list3->AddTail(strread);
    break;
    case 3:
    list4->AddTail(strread);
    break;
    case 4:
    list5->AddTail(strread);
    break;
    case 5:
    list6->AddTail(strread);
    break;
    case 6:
    list7->AddTail(strread);
    break;

    case 7:
    list8->AddTail(strread);
    break;

    default:
    break;
    }
    num=0;
    // 计算值

    }while(bRet);

    filein.Close();
    }
    else
    {
    MessageBoxA("打开文件失败!",NULL,MB_OK);
    return;
    }
    // 把结果复制到 strtem
    fileindex=0;
    if (!list2->IsEmpty()||!list3->IsEmpty()||!list4->IsEmpty()||!list5->IsEmpty()||!list6->IsEmpty()||!list7->IsEmpty()||!list8->IsEmpty())
    {
    while (fileindex<list2->GetCount())
    {

    te = list2->GetAt(list2->FindIndex(fileindex++));
    if (te.IsEmpty())
    {
    break;
    }
    else
    {
    strtem+=te;
    strtem+='\n';
    }
    }
    fileindex=0;
    while (fileindex<list3->GetCount())
    {

    te = list3->GetAt(list3->FindIndex(fileindex++));
    if (te.IsEmpty())
    {
    break;
    }
    else
    {
    strtem+=te;
    strtem+='\n';
    }
    }
    fileindex=0;
    while (fileindex<list4->GetCount())
    {

    te = list4->GetAt(list4->FindIndex(fileindex++));
    if (te.IsEmpty())
    {
    break;
    }
    else
    {
    strtem+=te;
    strtem+='\n';
    }
    }
    fileindex=0;
    while (fileindex<list5->GetCount())
    {

    te = list5->GetAt(list5->FindIndex(fileindex++));
    if (te.IsEmpty())
    {
    break;
    }
    else
    {
    strtem+=te;
    strtem+='\n';
    }
    }
    fileindex=0;
    while (fileindex<list6->GetCount())
    {

    te = list6->GetAt(list6->FindIndex(fileindex++));
    if (te.IsEmpty())
    {
    break;
    }
    else
    {
    strtem+=te;
    strtem+='\n';
    }
    }
    fileindex=0;
    while (fileindex<list7->GetCount())
    {

    te = list7->GetAt(list7->FindIndex(fileindex++));
    if (te.IsEmpty())
    {
    break;
    }
    else
    {
    strtem+=te;
    strtem+='\n';
    }
    }
    fileindex=0;
    while (fileindex<list8->GetCount())
    {

    te = list8->GetAt(list8->FindIndex(fileindex++));
    if (te.IsEmpty())
    {
    break;
    }
    else
    {
    strtem+=te;
    strtem+='\n';
    }
    }
    } //保存结果到txt
    if (fileout.Open(strSavePath,CFile::modeCreate|CFile::modeReadWrite | CFile::typeText))
    {
    fileout.WriteString(strtem);
    fileout.Close();
    }
    else
    {
    MessageBoxA("创建保存文件失败!","提示!",MB_OK);
    return;
    }
    ///////---------------------------------另外的方法按行读取txt内容--------------------------------------///////// /*
    //通过Notpad++ 看所有字符,知道行末换行符为 CRLF
    //换行符
    carriage return = \r
    line feed = \n
    */
    delete list2;
    delete list3;
    delete list4;
    delete list5;
    delete list6;
    delete list7;
    delete list8;
    }
      

  9.   

    从你的代码看
    你并没有把所有内容全都读到内存
    而是一次读一行
    bRet = filein.ReadString(strread); //一次读取一行问题也就出在这
      

  10.   

    谢谢,这么早就关注我的问题了,这个是之前写的,下面这个是后来改为一次读取进内存后操作的。下面这个是用单线程里调用的函数,一次读取到内存后再操作的
    DWORD WINAPI THREADPRO(LPVOID data)
    {

    //===========================================================
    CString strtem="",te="";
    CString list_noconvert="",m2="",m3="",m4="",m5="",m6="",m7="",m8="";
    DWORD num_noconvert=0;
    int num=0,index=0;
    int fileindex=0,totalnum=0;
    //========================================================================
    char *buffer=(char *)data;
    char *ttt=NULL;
    CString strread;
    BOOL bRet=FALSE;
    ///=====================================================
    CString test="",sub="";
    test.Format(buffer);
    int temnum=0;

    for (DWORD i=0;i<pDlg->linenum;i++)
    {
    temnum=test.FindOneOf(_T("\r\n"));
    sub.Format(test.Left(temnum));
    //----------------------------------------------------------------
    // 串处理 .Find; .TrimLeft .TrimRight .Left
    ttt=(LPSTR)(LPCTSTR)sub;
    for (int k=0;k<sub.GetLength();k++)
    {
    if (ttt[k]=='|')
    {
    num++;
    }
    }
    //pDlg->MessageBoxA(sub,"提示!",MB_OK);
    switch (num)
    {
    case 1:
    //list2->AddTail(sub);
    m2+=sub;
    m2+='\n';
    break;
    case 2:
    m3+=sub;
    m3+='\n';
    //list3->AddTail(sub);
    break;
    case 3:
    m4+=sub;
    m4+='\n';
    //list4->AddTail(sub);
    break;
    case 4:
    m5+=sub;
    m5+='\n';
    //list5->AddTail(sub);
    break;
    case 5:
    m6+=sub;
    m6+='\n';
    //list6->AddTail(sub);
    break;
    case 6:
    m7+=sub;
    m7+='\n';
    //list7->AddTail(sub);
    break;
    case 7:
    m8+=sub;
    m8+='\n';
    //list8->AddTail(sub);
    break;

    default:
    break;
    }
    num=0;
    // 计算值 //----------------------------------------------------------------
    test=test.Right(test.GetLength()-temnum-1);

    } if (finish_1&&finish_2&&finish_3&&finish_4&&finish_5&&finish_6&&finish_7)
    {
    finish_=TRUE;
    myres=m1+m2+m3+m4+m5+m6+m7;
    }

    return 0;}
      

  11.   

    单线程里调用时的参数 (LPVOID data),就是内存中存放读取到的文本文件的所有内容的内存地址。就是,在调用这个函数前,先打开文件,一次性读取所有内容到一个用 new 申请的字符数组里,new 的数组大小,是这个文件的长度,我打开文件时的模式 是用 “文本模式” 来打开的
      

  12.   

    这个函数最后得到的结果  myres=m1+m2+m3+m4+m5+m6+m7;myres  就是所有字符串排好序的最终结果,也就是,2个 ‘|’的行排在前面,接着是 3个 ‘|’的行。
      

  13.   

    感觉上应该是CString的操作会比较耗时
    尤其是字符串比较长的时候
    你可以考注释掉某些语句再运行下
    看看什么语句耗时比较严重
      

  14.   

    如果不用字符串CString 来操作的话,那用 字符数组么? 数组的话,那就每行都要遍历来比较找出 ‘|’的个数了,如果不用字符数组,那用什么比较啊?如果用二进制格式读取进来处理,会不会快点?要是二进制的话,判断字符,岂不是麻烦?
      

  15.   

    CString内部是动态分配内存的
    申请内存有可能会浪费时间
    我就是建议一下
    先看看是不是这方面的问题
    而且字符在内存里本来就是数
    没什么麻烦的
      

  16.   

    1、strread 使用 Find 函数(strchr) 而不是一个一个字符进行分析不好么?2、test=test.Right(test.GetLength()-temnum-1); 这句话没必要,不要使用 CSting,修改成处理 LPCSTR 吧,这样使用 CString 就是在给自己找麻烦,文件越大越耗时间。或者你记录 position 使用 CString 的 Find(char, int) 吧;
      

  17.   


    你留个联系方式,Email 或者QQ ,我发给你试试
      

  18.   

    然后把它存储到专门存储 n 个 ‘|’字符的字符串里
    --------------------------------------------有可能是内存操作太频繁, 预先分配一个大的空间试试string::reserve
      

  19.   

    更直接一点说:就是不要让临时字符串产生内存分配动作在操作前,估算最大的可能需求空间,然后用GetBufferSetLength设置它可能的最大空间。
    运算完成后ReleaseBuffer释放多的空间,得到完整的字符串。速度就提上去了。
      

  20.   

    终于搞定了,
    谢谢大家的帮助,特别是57楼的 varding 
    我用他的程序,测试过550万行的txt数据(195MB 左右),排序完毕并且写入结果txt中,用时为 13.5秒 左右,不过他Email 给我的程序里面,写入txt文本的那种方法,不行,即使15MB 的txt处理完后,程序都会崩溃。我修改了他的这段写入txt的方法,一次性写入文件。
    下面是整个实现的代码:(我修改后的)
    //-------------------------------------------------------typedef vector<vector<_line_info>> lineArr;
    void CTestGuiDlg::OnBnClickedOk()
    {
    int _start = GetTickCount();
    CFile _file;
    _file.Open("C:\\test.txt",CFile::modeRead); //文件长度;
    int _len = _file.GetLength();
    char* pdata = new char[_len+1];
    char* presdata= new char[_len+1];
    //读文件;
    _file.Read(pdata,_len);
    pdata[_len] = 0;
    _file.Close(); lineArr _line_arr; _line_arr.resize(100);        //假设最多一行有10个'|' int cnt = 0; _line_info _info;
    int _head_pos = 0;
    for(int i = 0;i < _len;)
    {
    if(pdata[i] == '\r')                        //回车;
    {        
    i += 2;
    _info.pos = _head_pos; //这一行的起点;
    _info.len = i - _head_pos; //这一行的长度;
    _line_arr[cnt-1].push_back(_info); _head_pos = i; //下一行起点;
    cnt = 0; //'|'计数清0
    continue;
    }
    else if(pdata[i] == '|')
    ++cnt;
    ++i;
    }
    /*
    //这段是 57楼那位朋友的写入文件的程序
    //写文件;
    CFile _file2;
    _file2.Open("C:\\test保存的结果.txt",CFile::modeWrite); //遍历并且写文件;
    for (lineArr::iterator it = _line_arr.begin();it != _line_arr.end();++it)
    {
    for (vector<_line_info>::iterator it2 = it->begin();it2 != it->end();++it2)
    {
    _file2.Write(pdata + it2->pos,it2->len);
    }
    }
    */
    //下面这个是我修改的,一次性写入,速度快,不用频繁的写入。
       char *ptem=presdata;
       //遍历复制到结果缓存区;
       for (lineArr::iterator it = _line_arr.begin();it != _line_arr.end();++it)
       {
          for (vector<_line_info>::iterator it2 = it->begin();it2 != it->end();++it2)
          {
     memcpy(ptem,pdata + it2->pos,it2->len);
    ptem+=it2->len;
    }
       }
       CFile _file2;
       _file2.Open("C:\\test保存的结果.txt",CFile::modeCreate|CFile::modeWrite);
       _file2.Write(presdata,_len+1);
       _file2.Close(); delete[] pdata;
    //消耗时间;
    int _end = GetTickCount();
    int _time = _end - _start;

    char buf[16];
    sprintf(buf,"消耗时间:%d 毫秒",_time);
    MessageBox(buf);
      

  21.   

    65 楼那段程序,最后释放内存那里,漏写了: delete []presdata;
      

  22.   

    1 首先,CStdioFile尽管方便,但效率是很低的文件读取方法,原因是磁盘操作时寻道时间、潜伏时间等问题,所以还是一次性把所有文件内容全部读取到内存中,几十M的东西没必要用内存映射文件这种方法,何况我们要读取全部的内容,更没有用这个方法的必要;
    2 其次,把内存中文件内容手工解析,按行符分解成一个个子串(动态创建char **数组吧),一个子串一行内容。另外动态创建两个同样大小的int类型数组,其中一个用来存放每个子串中“|”的个数,原因:你也不想每次比较时都重新数一数“|”的个数吧,另一个留着后面用来保存子串的索引号;
    3 再次,对前面这个int数组进行排序吧,按数值大小的顺序,把它们的数组索引依次保存到后面一个int数组中;
    4 清空内存中文件内容,然后按后面一个数组中保存的子串索引,再逐一把子串添加到这块内存中;
    5 最后,把该块内存的内容写入磁盘中。收工提高效率的关键之处是:
    1 只有一个读取文件的操作,同样的也仅有一个写入文件的操作;
    2 每行“|”的个数我们只数一遍,绝不数第二遍;
    3 排序的算法,这个我就不多说了。
      

  23.   


    我是从最基础的学起的,到CSDN 这里就是学习的,当然也希望某天可以帮助别人,哈哈,谢谢您