13

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

abc
def
ghi

应按行阅读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。*

4

5 回答 5

10

您可以通过程序通过管道输入输入tac,这就像cat但倒退!

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

于 2013-02-12T14:30:42.403 回答
8

我推荐一种更便携(希望)的文件大小确定方式,因为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 上对此进行了测试。

于 2013-02-12T18:16:04.487 回答
4

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

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

另一种选择是mmap文件,只是假装文件是一块内存,然后向后扫描。[您也可以告诉mmap您正在向后阅读,以使其为您预取数据]。

如果文件非常大(几千兆字节),您可能只想在mmap.

于 2013-02-12T14:33:31.463 回答
1

如果您想学习如何操作,这里有一个 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源文件中。

于 2013-02-12T15:15:05.340 回答
0

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

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

另一种选择是 Windows 内存映射文件。

于 2013-02-12T14:34:11.240 回答