【问题标题】:read file backwards (last line first)向后读取文件(最后一行在前)
【发布时间】:2011-10-18 20:47:16
【问题描述】:

文件如下所示:

abcd



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

jkl


abcd

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

edit0: 感谢所有的答案。只是让您知道,我是创建此文件的人。那么,我可以以相反的顺序创建它吗?这可能吗?

【问题讨论】:

  • 已经存在一个名为tac 的命令行实用程序(cat 的反面)。您可以为此获取源代码并研究该程序如何解决问题。
  • 有什么理由让您因为无法使用数组而自责?
  • @hlovdal:谢谢,正在调查tac 来源。
  • @mu 太短:因为行数可以巨大

标签: c file


【解决方案1】:

是这样的:

  1. 使用fseek 查找文件末尾前的一个字节。不能保证最后一行会有 EOL,所以最后一个字节并不重要。
  2. 使用fgetc读取一个字节。
  3. 如果该字节是 EOL,那么最后一行是一个空行,您就拥有了。
  4. 再次使用fseek 倒退两个字节并使用fgetc 检查该字节。
  5. 重复上述操作,直到找到 EOL。当您有 EOL 时,文件指针将位于下一行(从末尾开始)的开头。
  6. ...
  7. 利润。

基本上,您必须继续执行 (4) 和 (5),同时跟踪找到行首时所在的位置,以便在开始扫描下一行的开头之前回溯到那里。

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

如果您碰巧获得了不可搜索的输入(例如管道),那么除非您想先将输入转储到临时文件中,否则您将不走运。

所以你可以做到,但它相当丑陋。

如果您有可用的mmap 并且您正在使用的“文件”是可映射的,那么您可以使用mmap 和指针来做几乎相同的事情。该技术几乎相同:从末尾开始并向后查找上一行的结尾。


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

您会遇到同样的问题,但情况会更糟。 C 中的文件本质上是从头到尾的顺序字节列表;你试图违背这个基本属性,而违背基本原理从来都不是一件有趣的事情。

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

【讨论】:

  • 您的解决方案效率不高,因为fseek 是一个缓慢的操作,您正在为文件中的每个字节执行此操作。
  • @Skizz:我从来没有说过它是有效的,但我确实说它很丑。你有什么更好的不使用数组的吗?我想总是有mmap
  • 标准的f* IO函数,当FILE *以文本(非二进制)模式打开时,会自动为你做EOL转换。
  • @Chris:好的,谢谢。自从我不得不在 Windows 上处理 C 以来已经有很长时间了。
【解决方案2】:

在伪代码中:

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

上面是高效的,没有seek(一个缓慢的操作)并且文件是顺序读取的。但是,上面有两个陷阱。

第一个是fgets 电话。提供给fgets 的缓冲区可能不足以容纳输入的整行,在这种情况下,您可以执行以下操作之一:再次读取并连接;推送部分行并在后半部分添加逻辑以修复部分行或将行包装到链表中,并且仅在遇到换行符/eof 时才推送链表。

当文件大于可用内存来保存堆栈时,会发生第二个陷阱,在这种情况下,只要达到某个阈值内存使用量,您就需要将堆栈结构写入临时文件。

【讨论】:

  • 这个答案在很多方面都很糟糕:它使用了大量的内存而没有必要。寻找并不慢。也许在 Windows 或网络文件系统中,但不是在 Unix 上。
  • @Coroos:没错,seek 函数本身并不慢,只是用它来向后读取文件,即你在哪里寻找?从结尾到开头一次读取一个字符?这可以做到,而且做得很好,但是代码开始变得有点复杂。这里,“效率”也是代码复杂度的衡量标准,这段代码是微不足道的。
  • 从末端向前读取数据块,然后向后扫描块更复杂。存储介质可能比代码更能影响算法的速度,向前读取文件永远不会变慢(并不总是更快,但永远不会变慢)。但是,当可用内存有限时,向后读取文件的方法会更好。秋千和环形交叉路口。
【解决方案3】:

下面的代码应该做必要的反转:

#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);
}

【讨论】:

    【解决方案4】:

    以下内容适用于 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)
    【解决方案5】:

    我知道这个问题已被 awnsered,但接受的 awnser 不包含代码 sn-p 并且其他 sn-ps 感觉太复杂了。 这是我的实现:

    #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 想要反转行
    【解决方案6】:

    也许,这个诀窍,它反转整个文件的内容 就像一个字符串

    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,因此不应该被推荐。还需要围绕文件大小进行编码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多