在 C 中向后读取文本文件

Posted

技术标签:

【中文标题】在 C 中向后读取文本文件【英文标题】:Reading a text file backwards in 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 我相信操作人员正在尝试弄清楚如何做到这一点。 【参考方案1】:

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

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

【讨论】:

你当然可以在 t'internet 的某个地方找到tac 的源代码。例如这里:git.savannah.gnu.org/cgit/coreutils.git/tree/src/tac.c【参考方案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 思维方式中。

以上是关于在 C 中向后读取文本文件的主要内容,如果未能解决你的问题,请参考以下文章

为啥必须在 C 中向后读取指针声明? [关闭]

关于Java中向文件写入数据的问题

使文本在flexbox的第一个单元格中向左对齐?

如何在Java中向后打印多个字符串

在c ++中向/从文件读取写入变量

如何使用PHP读取文本文件内容