读取文件的倒序(最后一行先读取)

8

文件看起来像这样:

abcd
efgh
ijkl

我想使用 C 读取文件,使其首先读取最后一行:

ijkl
efgh
abcd

我似乎找不到不使用 array 存储的解决方案。请帮忙。

编辑0: 感谢所有回答者。只是想让你们知道,我是创建这个文件的人。所以,我能否以相反的顺序创建它?这可行吗?


请参考以下链接:https://dev59.com/jXVC5IYBdhLWcg3w9GHM - user149341
3
已经存在一个称为“tac”(cat的反转)的命令行实用程序。你可以获取该程序的源代码并研究它是如何解决问题的。 - hlovdal
1
你不使用数组的原因是什么?这样做会限制你自己的能力。 - mu is too short
@hlovdal:谢谢,正在查看tac源代码。 - hari
@mu太短了:因为行数可能会非常巨大。 - hari
6个回答

13

操作步骤如下:

  1. 使用fseek函数将文件指针定位到文件末尾前一个字节。由于最后一行可能没有EOL(行尾符),因此最后一个字节并不重要。
  2. 使用fgetc函数读取一个字节。
  3. 如果该字节是EOL,则最后一行是单个空行,您已经找到了它。
  4. 再次使用fseek函数向后移动两个字节,并使用fgetc函数检查该字节。
  5. 重复上述步骤,直到找到EOL。当您找到EOL时,文件指针将位于下一行(从末尾开始算)的开头。
  6. ...
  7. 完成。

基本上,您需要在执行第(4)和(5)个步骤时记录您找到行开头的位置,以便在开始扫描下一行之前回到那里。

只要以文本模式打开文件,您就不必担心Windows上的多字节EOL(感谢Mr. Lutz提醒)。

如果您收到不可寻址的输入(例如管道),那么您就没办法了,除非您想先将输入转储到临时文件中。

因此,您可以这样做,但它相当丑陋。

如果您有mmap函数可用并且您正在处理的“文件”可映射,则可以使用mmap和指针执行几乎相同的操作。技术基本相同:从末尾开始向后移动,以找到上一行的结尾。


回复:“我是创建这个文件的人。那么,我能以相反的顺序创建它吗?这是可能的吗?”

你会遇到同样类型的问题,但会更加严重。在 C 中,文件本质上是一个从开头到结尾的顺序字节列表;你正在努力反对这个基本属性,而违反基本原则从来都不是一件有趣的事情。

你真的需要把数据存储在纯文本文件中吗?也许最终输出需要使用 text/plain,但是否一直如此呢?你可以将数据存储在索引二进制文件中(甚至是 SQLite 数据库),然后只需担心保留(或分段)索引即可,这不太可能成为问题(如果确实存在问题,请使用“真正”的数据库);然后,当你有了所有的行时,只需反转索引即可。


3
你的解决方案不够高效,因为fseek是一个缓慢的操作,而你正在对文件中的每个字节执行它。 - Skizz
@Skizz:我从未说过它是高效的,但我确实说过它很丑陋。你有没有更好的方法,而不使用数组?我想也许可以用mmap - mu is too short
2
标准的 f* IO 函数,当以文本(非二进制)模式打开 FILE * 时,会自动为您执行 EOL 转换。 - Chris Lutz
@Chris:好的,谢谢。很久以前我就没有在Windows上处理C语言了。 - mu is too short

3

伪代码中:

open input file
while (fgets () != NULL)
{
   push line to stack
}
open output file
while (stack no empty)
{
   pop stack
   write popped line to file
}

上述方法是高效的,没有“寻找”(一种缓慢的操作),文件按顺序读取。然而,上述方法有两个陷阱。
第一个陷阱是fgets调用。提供给fgets的缓冲区可能不足以容纳输入中的整行内容,此时可以进行以下操作之一:再次读取并连接;推送部分行并添加逻辑来修复部分行或将该行包装成链接列表,并在遇到换行符/文件结尾时仅推送链接列表。
第二个陷阱发生在文件大于可用RAM以容纳堆栈时,此时需要在达到某个阈值内存使用情况时将堆栈结构写入临时文件。

1
这个答案有很多问题:它在没有必要的情况下使用了大量内存。寻找并不慢。也许在Windows或网络文件系统中会慢,但在Unix上不会。 - Coroos
@Coroos:没错,seek函数本身并不慢,只是用它来倒序读取文件时会变慢,比如你要seek到哪里?从末尾开始一个字符一个字符地读取文件吗?这是可以做到的,而且做得很好,但代码开始变得有点复杂了。在这里,“高效”也是代码复杂度的一种衡量标准,而这段代码非常简单。 - Skizz
从末尾向前读取数据块,然后向后扫描这些块会更加复杂。存储介质可能会比代码更影响算法的速度,正向读取文件永远不会更慢(不一定更快,但永远不会更慢)。当可用内存有限时,反向读取文件的方法将更为优越。得失参半。 - Skizz

2
以下代码应该完成所需的反转操作:
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        FILE *fd;
        char len[400];
        int i;

        char *filename = argv[1];
        int ch;
        int count;

        fd = fopen(filename, "r");
        fseek(fd, 0, SEEK_END);
        while (ftell(fd) > 1 ){
                fseek(fd, -2, SEEK_CUR);
                if(ftell(fd) <= 2)
                        break;
                ch =fgetc(fd);
                count = 0;
                while(ch != '\n'){
                        len[count++] = ch;
                        if(ftell(fd) < 2)
                                break;
                        fseek(fd, -2, SEEK_CUR);
                        ch =fgetc(fd);
                }
                for (i =count -1 ; i >= 0 && count > 0  ; i--)
                        printf("%c", len[i]);
                printf("\n");
        }
        fclose(fd);
}

-1

以下代码适用于Linux系统,在该系统中文本文件的行分隔符为"\n"。

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

void readfileinreverse(FILE *fp)
{
    int i, size, start, loop, counter;
    char *buffer;
    char line[256];
    start = 0;
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);

    buffer = malloc((size+1) * sizeof(char));

    for (i=0; i< size; i++)
    {
        fseek(fp, size-1-i, SEEK_SET);
        buffer[i] = fgetc(fp);

        if(buffer[i] == 10)
        {
           if(i != 0)
           {
            counter = 0;        
            for(loop = i; loop > start; loop--)
            {
                if((counter == 0) && (buffer[loop] == 10))
                {
                    continue;
                }               
                line[counter] = buffer[loop];
                counter++;
            }
            line[counter] = 0;
            start = i;
            printf("%s\n",line);
           }
        }
    }

    if(i > start)
    {    
        counter = 0;
        for(loop = i; loop > start; loop--)
        {       
            if((counter == 0) && ((buffer[loop] == 10) || (buffer[loop] == 0)))
            {
                continue;
            }               
            line[counter] = buffer[loop];
            counter++;
        }
        line[counter] = 0;
        printf("%s\n",line);

        return;
    }
}

int main()
{
    FILE *fp = fopen("./1.txt","r");
    readfileinreverse(fp);
    return 0;
}

我觉得你的程序存在内存泄漏问题。你使用malloc()buffer分配内存,但在函数readfileinreverse()结束时没有调用free(buffer)释放内存。 - iamantony

-2

我知道这个问题已经得到了回答,但是被接受的回答没有包含代码片段,而其他片段则感觉过于复杂。 这是我的实现:

#include <stdio.h>

long file_size(FILE* f) {
    fseek(f, 0, SEEK_END); // seek to end of file
    long size = ftell(f); // get current file pointer
    fseek(f, 0, SEEK_SET); // seek back to beginning of file
    return size;
}

int main(int argc, char* argv[]) {
    FILE *in_file = fopen(argv[1], "r");
    long in_file_size = file_size(in_file);
    printf("Got file size: %ld\n", in_file_size);

    // Start from end of file
    fseek(in_file, -1, SEEK_END); // seek to end of file
    for (int i = in_file_size; i > 0; i--) {
        char current_char = fgetc(in_file); // This progresses the seek location
        printf("Got char: |%c| with hex: |%x|\n", current_char, current_char);
        fseek(in_file, -2, SEEK_CUR); // Go back 2 bytes (1 to compensate)
    }
    printf("Done\n");

    fclose(in_file);
}

该程序将翻转每个字符,而PO想要翻转行。 - Nissim Levy

-2

也许,这就是诀窍,它可以完整地反转文件的内容,就像一个字符串一样

  1. 定义一个大小为文件大小的字符串类型变量
  2. 获取文件内容并存储在变量中
  3. 使用strrev()函数来反转字符串。

您可以随后显示输出甚至将其写入文件。代码如下:

#include <stdio.h>
#include <String.h>

int main(){
    FILE *file;
    char all[1000];

    // give any name to read in reverse order
    file = fopen("anyFile.txt","r");

    // gets all the content and stores in variable all
    fscanf(file,"%[]",all);

    // Content of the file 
    printf("Content Of the file %s",all);

    // reverse the string 
    printf("%s",strrev(all));
    fclose(file);
    return 0;
}

strrev不是标准的C语言,因此不应该建议使用它。此外,还需要编写处理文件大小的代码。 - Andrew
此外,文件可以是任何大小。 - Soumya Kanti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接