使用 C++ 将最后 $N $ 行打印到文件时出现“fseek”问题

`fseek` issue when printing last $N$ lines to a file using C++

提问人:ubaabd 提问时间:6/11/2023 更新时间:6/11/2023 访问量:57

问:

我正在使用以下代码将一个文件的最后一行打印到另一个文件。N

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

void printLastNLines(const std::string& inputFileName, const std::string& outputFileName, int N);
int main()
{
    printLastNLines("test.csv", "test2.csv", 200);

}
void printLastNLines(const std::string& inputFileName, const std::string& outputFileName, int N) {
    FILE* in, * out;
    int count = 0;
    long int pos;
    char s[100];

    fopen_s(&in, inputFileName.c_str(), "rb");
    /* always check return of fopen */
    if (in == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    fopen_s(&out, outputFileName.c_str(), "wb");
    if (out == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    
    
    fseek(in, 0, SEEK_END);
    pos = ftell(in);
    
    while (pos) {
        pos--;
        fseek(in, pos, SEEK_SET); 
        char c = fgetc(in);
        if (c == '\n') {
            if (count++ == N) break;
        }
    }
    //fseek(in, pos, SEEK_SET);
    /* 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);
}

示例文件的内容如下:test.csv

2
3

但是,当我运行代码时,包含以下内容(不是说第一行在那里,但不包含任何字符:test2.csv

3

谁能指导代码出了什么问题?一般来说,当我给它一个更大的文件时,第一行的第一个字符总是丢失。

我以为这与文件指针位置有关。因此,我使用了另一行(目前已注释掉)和第一行开始打印。但是,我不确定为什么它需要这个额外的.当我调试代码时,执行的最后一行实际上是 .为什么我们需要额外的东西来让它工作?fseek(in, pos, SEEK_SET);2fseekfseek(in, 0, SEEK_SET);fseek(in, 0, SEEK_SET);

C++ 文件 fgets fseek fgetc

评论

0赞 n. m. could be an AI 6/11/2023
fgetc将位置向前移动。
0赞 PaulMcKenzie 6/11/2023
我使用以下代码将一个文件的最后 N 行打印到另一个文件。-- 没有必要搞砸 ,或任何类似的函数来做到这一点。创建一个包含 N 个项目的队列,并在行中阅读,在后面推一个你阅读的新行。当队列已满时,将顶部项目取消排队,并将下一个读取行排队。冲洗并重复整个文件。完成后,队列将包含文件的最后 N 行。这可能是 10 或 15 行代码,并且有一点逻辑将队列的大小保持在始终为 N 个项目。fseekstd::queue
0赞 PaulMcKenzie 6/11/2023
下面是使用 std::queue 的示例。没有测试,但这基本上是我提到的大纲。
0赞 ubaabd 6/11/2023
@PaulMcKenzie 这是个好主意。但是,我正在对较大尺寸的较大文件执行相同的操作。队列或循环缓冲区方法将为此解决方案使用过多的内存。这就是为什么我尝试使用不需要缓冲区的解决方案。
0赞 PaulMcKenzie 6/11/2023
如果 200 是要保留的行数,那就非常小了。文件有多大?您知道文件数据/大小大致由什么组成吗?每行字符数?另一种解决方案是寻找到最后,减去您认为将包含文本行的一定数量,寻找文件中的那个位置,然后从文件中的那个位置开始队列/缓冲区解决方案。即使你的猜测不对,你也可以从你猜到的地方开始一些行,重新开始这个过程。换言之,从色调上确定从哪里开始读取。Nx

答:

0赞 Gurnet 6/11/2023 #1

该解决方案基本上已经在您的源代码中可见。但是你把它注释掉了://fseek(in, pos, SEEK_SET);

根本原因是你用来读取一个字符并将其与换行符进行比较。如果你找到一个 which read before,那么文件指针是 之后的一个。然后终止循环,文件指针位于错误的位置。getc\n\n\n

因此,您可以取消注释您的陈述,但这也不可靠。根据您的操作系统,新行可能标有 ,因此回车符和换行符。对于您的系统来说,这很可能是正确的。然后搜索操作也不会做你所期望的。//fseek(in, pos, SEEK_SET);\r\n

因此,没有简单的便携式解决方案。有一个快速修复,但不建议这样做。您可以尝试根据标记的行尾的专有技术来设置文件指针。

但是,如果您将 C++ 用于解决方案,那就更好了。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std::string_literals;

void printLastNLines(const std::string& inputFileName, const std::string& outputFileName, int N) {
    // Open files and check, if they could be opened
    if (std::ifstream ifs(inputFileName); ifs) 
        if (std::ofstream ofs(outputFileName); ofs) {

            // We will read all lines into a vector
            std::vector<std::string> lines{};
            for (std::string line{}; std::getline(ifs, line); lines.push_back(line));

            // If N is greater then number of lines that we read, then limit the value
            size_t numberOfLines = N < 0 ? 0 : N;
            if (numberOfLines > lines.size()) numberOfLines = lines.size();

            // And now we write the lines to the output file
            for (size_t i = lines.size() - numberOfLines; i< lines.size(); ++i)
                ofs << lines[i] << '\n';
        }
        else std::cout << "\nError: Coud not open input file '" << inputFileName << "'\n";
    else std::cout << "\nError: Coud not open output file '" << outputFileName << "'\n";
}
int main() {
    printLastNLines("r:\\test.csv"s, "r:\\test2.csv"s, 2);
}