在 C 中向后读取文本文件
Posted
技术标签:
【中文标题】在 C 中向后读取文本文件【英文标题】:Reading a text file backwards in C 【发布时间】:2013-01-27 20:39:30 【问题描述】:在 C 中反向读取文件的最佳方法是什么?我知道一开始您可能会认为这没有任何用处,但大多数日志等都会在文件末尾附加最新数据。我想从文件中向后读取文本,将其缓冲成行-即
abc 定义 吉
应该按行读取ghi、def、abc。
到目前为止我已经尝试过:
#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 中向后读取文本文件的主要内容,如果未能解决你的问题,请参考以下文章