【问题标题】:Reading a text file backwards in C在 C 中向后读取文本文件
【发布时间】:2013-01-27 20:39:30
【问题描述】:

在 C 中反向读取文件的最佳方法是什么?我知道一开始您可能会认为这没有任何用处,但大多数日志等都会在文件末尾附加最新数据。我想从文件中向后读取文本,将其缓冲成行-即

abc
定义

应该按行读取ghidefabc

到目前为止我已经尝试过:

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

    void read_file(FILE *fileptr)
    {
        char currentchar = '\0';
        int size = 0;

        while( currentchar != '\n' )
        {
            currentchar = fgetc(fileptr); printf("%c\n", currentchar);
            fseek(fileptr, -2, SEEK_CUR);
            if( currentchar == '\n') { fseek(fileptr, -2, SEEK_CUR); break; }
            else size++;

        }
        char buffer[size]; fread(buffer, 1, size, fileptr);
        printf("Length: %d chars\n", size);
        printf("Buffer: %s\n", buffer);


    }


    int main(int argc, char *argv[])
    {
        if( argc < 2) { printf("Usage: backwards [filename]\n"); return 1; }

        FILE *fileptr = fopen(argv[1], "rb");
        if( fileptr == NULL ) { perror("Error:"); return 1; }

        fseek(fileptr, -1, SEEK_END); /* Seek to END of the file just before EOF */
        read_file(fileptr);


        return 0;


    }

试图简单地读取一行并缓冲它。对不起,我的代码很糟糕,我变得非常困惑。我知道您通常会为整个文件分配内存然后读取数据,但对于不断变化的大文件,我认为直接读取会更好(特别是如果我想在文件中搜索文本)。

提前致谢

* 抱歉忘了提到这将在 Linux 上使用,所以换行符只是没有 CR 的 NL。 *

【问题讨论】:

  • 您可以memory map the file,并使用指针算法“读取”文件。可能比必须不断地用文件指针来回跳转更简单。
  • 来自 C 标准:A binary stream need not meaningfully support fseek calls with a whence value of SEEK_END.
  • 也许您可以登录数据库而不是普通文件?
  • 不是一次读取整个文件,您可以从末尾开始大块读取它。
  • @VaughnCato 我相信操作人员正在尝试弄清楚如何做到这一点。

标签: c text stream standard-library


【解决方案1】:

您可以通过tac 程序将输入通过管道传输,这与cat 类似,但倒过来了!

http://linux.die.net/man/1/tac

【讨论】:

【解决方案2】:

有很多方法可以做到这一点,但一次读取一个字节绝对是较差的选择之一。

读取最后一个,比如 4KB,然后从最后一个字符返回到上一个换行符是我的选择。

另一种选择是mmap 文件,并假装该文件是一块内存,并在其中向后扫描。 [你可以告诉mmap你也在向后阅读,让它为你预取数据]。

如果文件非常大(几 GB),您可能只想使用 mmap 中文件的一小部分。

【讨论】:

  • 谢谢,我会尝试“映射”它
【解决方案3】:

每个字节的 FSEEK 听起来非常缓慢。

如果您有内存,只需将整个文件读入内存,然后将其反转或向后扫描。

另一个选项是 Windows 内存映射文件。

【讨论】:

  • 内存映射文件不是windows特有的功能:)
【解决方案4】:

如果您想学习如何操作,这里有一个 Debian/Ubuntu 示例(对于其他基于 RPM 的发行版,请根据需要进行调整):

~$ which tac
/usr/bin/tac
~$ dpkg -S /usr/bin/tac
coreutils: /usr/bin/tac
~$ mkdir srcs
~$ cd srcs
~/srcs$ apt-get source coreutils

(剪辑 apt-get 输出)

~/srcs$ ls
coreutils-8.13  coreutils_8.13-3.2ubuntu2.1.diff.gz  coreutils_8.13-3.2ubuntu2.1.dsc  coreutils_8.13.orig.tar.gz
~/srcs$ cd coreutils-8.13/
~/srcs/coreutils-8.13$ find . -name tac.c
./src/tac.c
~/srcs/coreutils-8.13$ less src/tac.c

这不是太长,超过 600 行,虽然它包含一些高级功能,并使用其他来源的功能,但反向行缓冲实现似乎在那个 tac.c 源文件中。

【讨论】:

    【解决方案5】:

    我推荐一种更便携(希望是)确定文件大小的方法,因为 fseek(binaryStream, offset, SEEK_END) 不能保证有效。请参阅下面的代码。

    我认为文件至少应该在内核级别进行最少的缓冲(例如,默认情况下每个文件至少缓冲一个块),因此搜索不应该产生大量额外的 I/O,并且应该只在内部推进文件位置.如果默认缓冲不满意,可以尝试使用setvbuf()来加速I/O。

    #include <limits.h>
    #include <string.h>
    #include <stdio.h>
    
    /* File must be open with 'b' in the mode parameter to fopen() */
    long fsize(FILE* binaryStream)
    {
      long ofs, ofs2;
      int result;
    
      if (fseek(binaryStream, 0, SEEK_SET) != 0 ||
          fgetc(binaryStream) == EOF)
        return 0;
    
      ofs = 1;
    
      while ((result = fseek(binaryStream, ofs, SEEK_SET)) == 0 &&
             (result = (fgetc(binaryStream) == EOF)) == 0 &&
             ofs <= LONG_MAX / 4 + 1)
        ofs *= 2;
    
      /* If the last seek failed, back up to the last successfully seekable offset */
      if (result != 0)
        ofs /= 2;
    
      for (ofs2 = ofs / 2; ofs2 != 0; ofs2 /= 2)
        if (fseek(binaryStream, ofs + ofs2, SEEK_SET) == 0 &&
            fgetc(binaryStream) != EOF)
          ofs += ofs2;
    
      /* Return -1 for files longer than LONG_MAX */
      if (ofs == LONG_MAX)
        return -1;
    
      return ofs + 1;
    }
    
    /* File must be open with 'b' in the mode parameter to fopen() */
    /* Set file position to size of file before reading last line of file */
    char* fgetsr(char* buf, int n, FILE* binaryStream)
    {
      long fpos;
      int cpos;
      int first = 1;
    
      if (n <= 1 || (fpos = ftell(binaryStream)) == -1 || fpos == 0)
        return NULL;
    
      cpos = n - 1;
      buf[cpos] = '\0';
    
      for (;;)
      {
        int c;
    
        if (fseek(binaryStream, --fpos, SEEK_SET) != 0 ||
            (c = fgetc(binaryStream)) == EOF)
          return NULL;
    
        if (c == '\n' && first == 0) /* accept at most one '\n' */
          break;
        first = 0;
    
        if (c != '\r') /* ignore DOS/Windows '\r' */
        {
          unsigned char ch = c;
          if (cpos == 0)
          {
            memmove(buf + 1, buf, n - 2);
            ++cpos;
          }
          memcpy(buf + --cpos, &ch, 1);
        }
    
        if (fpos == 0)
        {
          fseek(binaryStream, 0, SEEK_SET);
          break;
        }
      }
    
      memmove(buf, buf + cpos, n - cpos);
    
      return buf;
    }
    
    int main(int argc, char* argv[])
    {
      FILE* f;
      long sz;
    
      if (argc < 2)
      {
        printf("filename parameter required\n");
        return -1;
      }
    
      if ((f = fopen(argv[1], "rb")) == NULL)
      {
        printf("failed to open file \'%s\'\n", argv[1]);
        return -1;
      }
    
      sz = fsize(f);
    //  printf("file size: %ld\n", sz);
    
      if (sz > 0)
      {
        char buf[256];
        fseek(f, sz, SEEK_SET);
        while (fgetsr(buf, sizeof(buf), f) != NULL)
          printf("%s", buf);
      }
    
      fclose(f);
      return 0;
    }
    

    我只在具有 2 个不同编译器的 Windows 上对此进行了测试。

    【讨论】:

    • 谢谢你,它真的很好用(我自己永远也想不出来,让我绞尽脑汁!)。
    • 没有问题。我已经修复了一个小错误,因此它现在可以正确处理最后一行不以 '\n' 结尾的情况(如果有的话,它最初会与上一行连接)。
    • @AlexeyFrunze,最后还需要免费的 buf 吗?
    • @scorpiozj 什么缓冲区?代码中是否有malloc()、calloc()或realloc()?
    • @AlexeyFrunze,知道了。我刚刚在 memcpy(man7.org/linux/man-pages/man3/memcpy.3.html) 中看到了“复制”,仍然处于一种 objc 思维方式中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-26
    • 1970-01-01
    相关资源
    最近更新 更多