在C中向后读取文件的最佳方法是什么?我知道一开始你可能会认为这没用,但大多数日志等都会在文件末尾附加最新的数据。我想从文件向后读取文本,将其缓冲为行 - 即
ABC
高清
GHI
应在行中读取 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;
}
试图简单地读取一行并缓冲它。对不起,我的代码很糟糕,我很困惑。我知道你通常会为整个文件分配内存,然后读入数据,但是对于经常更改的大文件,我认为最好直接读取(特别是如果我想在文件中搜索文本)。 / p>
提前致谢
*对不起忘了提到这将在Linux上使用,所以新行只是没有CR的NL。 *
答案 0 :(得分:10)
您可以通过程序tac
管道输入,类似于cat
但是向后!
答案 1 :(得分:7)
我建议使用更便携(希望)的文件大小确定方式,因为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上测试过这个。
答案 2 :(得分:4)
有很多方法可以做到这一点,但一次读取一个字节肯定是较差的选择之一。
读取最后一个,比如4KB,然后从最后一个字符向上走回到上一个换行符将是我的选择。
另一个选项是mmap
文件,只是假装文件是一块内存,然后向后扫描。 [你可以告诉mmap
你正在向后阅读,以便为你预取数据]。
如果文件非常大(几千兆字节),您可能只想在mmap
中使用该文件的一小部分。
答案 3 :(得分: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
(clip apt-get output)
~/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
源文件中
答案 4 :(得分:0)
每个字节的FSEEKing听起来很慢。
如果您有内存,只需将整个文件读入内存,然后将其反转或向后扫描。
另一种选择是Windows内存映射文件。