【问题标题】:Searching a text file backwards from the end从末尾向后搜索文本文件
【发布时间】:2019-11-22 09:48:59
【问题描述】:

我试图从末尾开始在文本文件中查找包含子字符串的字符串。该文件有数千万行。 (要求是从文件末尾读取。我不能使用 sed/awk/grep 等)

下面的程序可以完成这项工作,但需要很长时间。我怎样才能让它运行得更快?

#include <stdio.h>
#include <stdlib.h>

char *strrev(char *str)
{
      char *p1, *p2;

      if (! str || ! *str)
            return str;
      for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2)
      {
            *p1 ^= *p2;
            *p2 ^= *p1;
            *p1 ^= *p2;
      }
      return str;
}

int main(int argc, char *argv[])
{
        FILE *fd;
        char len[100];

        int ch,count;
        int fpos=0;

        char *filename = argv[1];

        fd = fopen(filename, "r");
        fseek(fd, 0, SEEK_END);
        fpos = ftell(fd);

        while (fpos > 0)
        {
                fseek(fd, -2, SEEK_CUR);
                if(ftell(fd) <= 2)
                        break;

                ch = fgetc(fd);
                fpos--;
                count = 0;
                memset( len, 0, sizeof(len));
                while(ch != '\n')
                {
                        len[count++] = ch;
                        if(ftell(fd) < 2)
                                break;
                        fseek(fd, -2, SEEK_CUR);
                        ch =fgetc(fd);
                }
                strrev(len);
                if( strstr(len,"TEST") !=NULL )
                {
                        printf("Found test. \n");
                        break;
                }
                //if(found)
                //do other stuff
        } 
        fclose(fd);
}

【问题讨论】:

  • 也许您可以使用fread 阅读大量文本。
  • 从EOF读取文件时如何读取完整的行?
  • 对文件中的每个字符使用fgetcftellfseek逐字符读取绝对是非常低效的。
  • 而且还为每一行调用strrev 效率低下。此外,“聪明的”异或交换的东西效率低下。您应该推出自己的“strstr”,直接搜索反向字符串。但无论如何,如果您阅读大量文本,您甚至不需要反转任何内容。

标签: c linux performance file


【解决方案1】:

正如评论员所指出的,一次执行 IO 一个字符(+ fseek + ftell)非常昂贵。请记住,每次代码fgets 第一个字符时,C 库都会“预读”——读取 BUFSIZ 字符。

作为替代方案,根据上面提供的想法考虑“先读”逻辑。

  • 找出模式的长度pl
  • 分配bs的缓冲区大小
  • 向后读取 bs 块。每个块将与前一个块重叠pl 个字符,从而消除块之间发生匹配的可能性。
  • 假设 bs 比 pl 大得多,重叠读取的成本最低,性能接近最佳。

减少错误检查以保持代码清晰。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define   BS 100000
int main(int argc, char **argv)
{
   FILE *fp = fopen(argv[1], "r");
   if ( !fp ) { perror("fopen") ; exit (1) ; };

   fseek(fp, 0, SEEK_END) ;
   long pos = ftell(fp) ;
   fprintf(stderr, "S=%ld\n", pos) ;

   const char *pattern = argv[2] ;
   const int PL = strlen(pattern) ;

       // Read backward
   char buff[BS+1] ;
   long match = -1;
   while ( pos > 0 ) {
       pos = pos - (BS-PL) ;
       if ( pos < 0 ) pos = 0 ;
       fseek(fp, pos, SEEK_SET) ;
       int n = fread(buff, sizeof(buff[0]), BS, fp) ;
       if ( n > 0 ) {
           buff[n] = 0 ;
           char *loc = strstr(buff, pattern) ;
           if ( loc ) ) { match = pos + (loc-buff) ; break ;} ;
       } ;
   } ;
   fclose(fp) ;
   printf("MATCH=%ld\n", match) ;
}

请注意,该解决方案将仅处理 TEXT 文件。 'strstr' 不适用于从可能包含 NUL 字符的二进制文件加载的数据。

在 2019 年 11 月 12 日更新以更正第一个块中匹配时的文件位置计算。

【讨论】:

  • 谢谢@dash-o。我如何获得确切的 fp 位置。即在找到模式匹配的行之后。我想我在块开始时获得了 fp 位置。
  • 还有其他要求吗?发布更新以根据块开始和缓冲区中的相对位置计算匹配的确切偏移量。请测试 - 我不在电脑旁
  • 谢谢。它适用于大文件,我还没有完全测试过。但是对于较小的文件,我认为 fp 位置是错误的。我想我们需要处理 -ve 条件。
  • @m4n07 已更正文件位置计算以匹配文件的第一个块。
  • 逻辑是正确的,但需要一些调整来处理“重叠”区域的读取——这有助于提高效率。给我一些时间考虑一下需要对您的代码进行哪些更改以适应块循环。
猜你喜欢
  • 2013-03-27
  • 1970-01-01
  • 2016-04-04
  • 2016-10-02
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多