【问题标题】:reading last n lines from file in c/c++在 C/C++ 中从文件中读取最后 n 行
【发布时间】:2013-07-26 08:54:01
【问题描述】:

我看过很多帖子,但没有找到我想要的。
我得到错误的输出:

ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ......  // may be this is EOF character

进入无限循环。

我的算法:

  1. 转到文件末尾。
  2. 指针位置减1,读字符减1 特点。
  3. 如果我们找到了 10 行或到达文件开头,则退出。
  4. 现在我将扫描整个文件直到 EOF 并打印它们//未在代码中实现。

代码:

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

using namespace std;
int main()
{
    FILE *f1=fopen("input.txt","r");
    FILE *f2=fopen("output.txt","w");
    int i,j,pos;
        int count=0;
        char ch;
        int begin=ftell(f1);
        // GO TO END OF FILE
        fseek(f1,0,SEEK_END);
        int end = ftell(f1);
        pos=ftell(f1);

        while(count<10)
        {
            pos=ftell(f1);
            // FILE IS LESS THAN 10 LINES
            if(pos<begin)
                break;
            ch=fgetc(f1);
            if(ch=='\n')
                count++;
            fputc(ch,f2);
            fseek(f1,pos-1,end);
        }
    return 0;
}

UPD 1:

更改的代码:它现在只有 1 个错误 - 如果输入有类似的行

3enil
2enil
1enil

it prints 10 lines only

line1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2

PS:
1. 在记事本++中处理windows

  1. 这不是家庭作业

  2. 我也想在不使用更多内存或使用 STL 的情况下这样做。

  3. 我正在练习以提高我的基础知识,所以请不要发布任何功能(如 tail -5 tc.)

请帮助改进我的代码。

【问题讨论】:

  • 提示:fgetc 将文件位置指示器提高一。
  • 试试 fseek(f1,pos-1, SEEK_SET); 和文件模式 bin。
  • C 还是 C++?选一个。 (提示:主要是 C。)
  • 是的,为什么不用fstream
  • @nkint 更重要的是:为什么不使用std::vector&lt;char&gt;,从一个相当大的值开始,并根据需要增加它?

标签: c++ file


【解决方案1】:

代码中的注释

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

int main(void)
{
    FILE *in, *out;
    int count = 0;
    long int pos;
    char s[100];

    in = fopen("input.txt", "r");
    /* always check return of fopen */
    if (in == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    out = fopen("output.txt", "w");
    if (out == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    fseek(in, 0, SEEK_END);
    pos = ftell(in);
    /* Don't write each char on output.txt, just search for '\n' */
    while (pos) {
        fseek(in, --pos, SEEK_SET); /* seek from begin */
        if (fgetc(in) == '\n') {
            if (count++ == 10) break;
        }
    }
    /* Write line by line, is faster than fputc for each char */
    while (fgets(s, sizeof(s), in) != NULL) {
        fprintf(out, "%s", s);
    }
    fclose(in);
    fclose(out);
    return 0;
}

【讨论】:

  • 你的实现很好,但我想知道如果我有非常非常大的号码,它会起作用(pos)。大约 20-30 GB 的行数和 characters.say 文件
  • 似乎您在 Windows 上,20-30 GB 可能是个问题,使用 _fseeki64_ftelli64,即使在 32 位 Windows 上也支持更长的文件偏移
【解决方案2】:

您的代码存在许多问题。最多 重要的是,您永远不会检查任何功能 成功了。并在int 中保存ftell 的结果不是 一个非常好的主意。然后是测试pos &lt; begin; 只有在出现错误时才会发生这种情况。而事实是 您将fgetc 的结果放在char 中(结果 在信息丢失的情况下)。而第一个读你的事实 do 在文件的末尾,所以会失败(一旦流进入 错误状态,它停留在那里)。而事实上你不能 可靠地对 ftell 返回的值进行算术运算(除了 在 Unix 下)如果文件以文本模式打开。

哦,没有“EOF 字符”; 'ÿ' 是完全有效的 字符(Latin-1 中的 0xFF)。一旦你分配了返回值 从fgetcchar,你已经失去了测试的任何可能性 文件结束。

我可能会补充说,一次向后阅读一个字符是 效率极低。通常的解决方案是分配 足够大的缓冲区,然后计算其中的'\n'

编辑:

只需一点代码即可给出这个想法:

std::string
getLastLines( std::string const& filename, int lineCount )
{
    size_t const granularity = 100 * lineCount;
    std::ifstream source( filename.c_str(), std::ios_base::binary );
    source.seekg( 0, std::ios_base::end );
    size_t size = static_cast<size_t>( source.tellg() );
    std::vector<char> buffer;
    int newlineCount = 0;
    while ( source 
            && buffer.size() != size
            && newlineCount < lineCount ) {
        buffer.resize( std::min( buffer.size() + granularity, size ) );
        source.seekg( -static_cast<std::streamoff>( buffer.size() ),
                      std::ios_base::end );
        source.read( buffer.data(), buffer.size() );
        newlineCount = std::count( buffer.begin(), buffer.end(), '\n');
    }
    std::vector<char>::iterator start = buffer.begin();
    while ( newlineCount > lineCount ) {
        start = std::find( start, buffer.end(), '\n' ) + 1;
        -- newlineCount;
    }
    std::vector<char>::iterator end = remove( start, buffer.end(), '\r' );
    return std::string( start, end );
}

这在错误处理方面有点弱;特别是,你 大概是想区分无法打开的 文件和任何其他错误。 (没有其他错误应该发生, 但你永远不知道。)

另外,这纯粹是 Windows,它假设实际 文件包含纯文本,并且不包含任何'\r' 不是 CRLF 的一部分。 (对于 Unix,只需将 最后一行。)

【讨论】:

  • 实际上我只是为了练习而编写代码,我想向后读取文件。它不是为了任何效率目的。它只是为了有信心处理文件。我学到了很多我不知道的东西。谢谢。
  • 嗯,重要的是在使用读取结果之前始终检查错误(我在示例代码中没有这样做),即fgetc (和istream::get()) 返回int,而不是char,以便返回带外EOF(也用于错误),并且任何错误条件都是粘性的:如果您看到错误,您必须先清除它,然后才能对流进行任何进一步的操作。如果您使用 C++,学习 iostream 会更好,因为它更加灵活和安全。
  • 正如您所指出的“在 int 中使用 ftell 也不是一个好主意”。位置=ftell(位置); if(pos
  • ftell 在 int 中不是一个好主意,因为该函数返回一个 long,并且将它放在一个 int 中可能会溢出。但是,如果您使用小文件进行测试,那不是您的问题。另一件事是long的值:在Windows(至少在二进制文件上)和Unix下,它是从文件开头开始的字节数;除非有错误,否则它永远不会小于零。
  • 代码中不小于begin;您不能定位在文件开头之前。
【解决方案3】:

这可以使用循环数组非常有效地完成。 不需要额外的缓冲区。

void printlast_n_lines(char* fileName, int n){

    const int k = n;
    ifstream file(fileName);
    string l[k];
    int size = 0 ;

    while(file.good()){
        getline(file, l[size%k]); //this is just circular array
        cout << l[size%k] << '\n';
        size++;
    }

    //start of circular array & size of it 
    int start = size > k ? (size%k) : 0 ; //this get the start of last k lines 
    int count = min(k, size); // no of lines to print

    for(int i = 0; i< count ; i++){
        cout << l[(start+i)%k] << '\n' ; // start from in between and print from start due to remainder till all counts are covered
    }
}

请提供反馈。

【讨论】:

    【解决方案4】:
    int end = ftell(f1);
    pos=ftell(f1);
    

    这告诉你文件的最后一点,所以 EOF。 当你阅读时,你得到EOF错误,并且ppointer想要向前移动1个空格......

    所以,我建议将当前位置减一。 或者将 fseek(f1, -2,SEEK_CUR) 放在 while 循环的开头,以弥补 fread 1 点并返回 1 点...

    【讨论】:

      【解决方案5】:

      我相信,你用错了fseek。在 Google 上查看man fseek

      试试这个:

      fseek(f1, -2, SEEK_CUR);
      //1 to neutrialize change from fgect
      //and 1 to move backward
      

      您还应该将开头的位置设置为最后一个元素:

      fseek(f1, -1, SEEK_END).
      

      您不需要end 变量。

      您应该检查所有函数的返回值(fgetcfseekftell)。这是一个很好的做法。我不知道这段代码是否适用于空文件或类似的东西。

      【讨论】:

      • $ man fseek 'man' 不是内部或外部命令、可运行程序或批处理文件。
      • @默认使用linux或Internet
      • @Ari 原发帖人明确表示自己在Windows下。 (即使在 Unix 下,如果您对可移植性感兴趣,我建议您使用 Posix 标准,而不是 man。虽然很多手册页指定什么是标准,什么是是扩展名。)
      • @JamesKanze 我的错。更新了将 man 源从本地更改为 Google。
      【解决方案6】:

      使用:fseek(f1,-2,SEEK_CUR);返回

      我写了这段代码,它可以工作,你可以试试:

      #include "stdio.h"
      
      int main()
      {
              int count = 0;
              char * fileName = "count.c";
              char * outFileName = "out11.txt";
              FILE * fpIn;
              FILE * fpOut;
              if((fpIn = fopen(fileName,"r")) == NULL )
                      printf(" file %s open error\n",fileName);
              if((fpOut = fopen(outFileName,"w")) == NULL )
                      printf(" file %s open error\n",outFileName);
              fseek(fpIn,0,SEEK_END);
              while(count < 10)
              {
                      fseek(fpIn,-2,SEEK_CUR);
                      if(ftell(fpIn)<0L)
                              break;
                      char now = fgetc(fpIn);
                      printf("%c",now);
                      fputc(now,fpOut);
                      if(now == '\n')
                              ++count;
              }
              fclose(fpIn);
              fclose(fpOut);
      }
      

      【讨论】:

      • 应该是 fseek(f1,-2,SEEK_CUR);
      • @anon 是因为我们使用widechar吗?
      • @LidongGuo,因为fgetc()消耗了1个位置
      • @anon O !我懂了!谢谢
      【解决方案7】:

      我会使用两个流来打印文件的最后 n 行: 这在 O(lines) runtimeO(lines) space 中运行。

      #include<bits/stdc++.h>
      using namespace std;
      
      int main(){
        // read last n lines of a file
        ifstream f("file.in");
        ifstream g("file.in");
      
        // move f stream n lines down.
        int n;
        cin >> n;
        string line;
        for(int i=0; i<k; ++i) getline(f,line);
      
        // move f and g stream at the same pace.
        for(; getline(f,line); ){
          getline(g, line);
        }
      
        // g now has to go the last n lines.
        for(; getline(g,line); )
          cout << line << endl;
      }
      

      具有 O(lines) 运行时间O(N) 空间 的解决方案正在使用队列:

      ifstream fin("file.in");
      int k;
      cin >> k;
      queue<string> Q;
      string line;
      for(; getline(fin, line); ){
        if(Q.size() == k){
          Q.pop();
        }
        Q.push(line);
      }
      while(!Q.empty()){
        cout << Q.front() << endl;
        Q.pop();
      }
      

      【讨论】:

        【解决方案8】:

        这是 C++ 中的解决方案。

        #include <iostream>                                                             
        #include <string>                                                               
        #include <exception>                                                            
        #include <cstdlib>                                                              
        
        int main(int argc, char *argv[])                                                
        {                                                                               
            auto& file = std::cin;                                                      
        
            int n = 5;                                                                  
            if (argc > 1) {                                                             
                try {                                                                   
                    n = std::stoi(argv[1]);                                             
                } catch (std::exception& e) {                                           
                    std::cout << "Error: argument must be an int" << std::endl;         
                    std::exit(EXIT_FAILURE);                                            
                }                                                                       
            }                                                                           
        
            file.seekg(0, file.end);                                                    
        
            n = n + 1; // Add one so the loop stops at the newline above                
            while (file.tellg() != 0 && n) {                                            
                file.seekg(-1, file.cur);                                               
                if (file.peek() == '\n')                                                
                    n--;                                                                
            }                                                                           
        
            if (file.peek() == '\n') // If we stop in the middle we will be at a newline
                file.seekg(1, file.cur);                                                
        
            std::string line;                                                           
            while (std::getline(file, line))                                            
                std::cout << line << std::endl;                                         
        
            std::exit(EXIT_SUCCESS);                                                    
        } 
        

        构建:

        $ g++ <SOURCE_NAME> -o last_n_lines
        

        运行:

        $ ./last_n_lines 10 < <SOME_FILE>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-17
          • 2015-04-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多