【问题标题】:Read and remove first (or last) line from txt file without copying从 txt 文件中读取并删除第一行(或最后一行)而不复制
【发布时间】:2016-06-13 21:50:35
【问题描述】:

我想读取并删除 txt 文件中的第一行(无需复制,这是一个巨大的文件)。
我已经阅读了网络,但每个人都只是将所需的内容复制到一个新文件中。我做不到。

低于第一次尝试。此代码将卡在一个循环中,因为没有删除任何行。如果代码在每次打开时删除文件的第一行,则代码将到达末尾。

#include <iostream>
#include <string>
#include <fstream>
#include <boost/interprocess/sync/file_lock.hpp>

int main() {
    std::string line;
    std::fstream file;
    boost::interprocess::file_lock lock("test.lock");
    while (true) {
        std::cout << "locking\n";
        lock.lock();
        file.open("test.txt", std::fstream::in|std::fstream::out);
        if (!file.is_open()) {
            std::cout << "can't open file\n";
            file.close();
            lock.unlock();
            break;
        }
        else if (!std::getline(file,line)) {
            std::cout << "empty file\n"; //
            file.close();                // never
            lock.unlock();               // reached
            break;                       //
        }
        else {
            // remove first line
            file.close();
            lock.unlock();
            // do something with line
        }
    }
}

【问题讨论】:

  • 文件不能这样工作(类似于原始数组,如果不将所有剩余元素向上移动一个槽,则无法删除第一个元素)。
  • if 语句的每个分支都有file.close(); lock.unlock();std::file 对象的析构函数将关闭文件,因此您无需显式关闭它(当file.is_open() 返回 false 时,无需关闭它)。毫无疑问,Boost 中有一个 RAII 类型来管理该锁,并使用析构函数来解锁它。
  • 我认为这个问题与截断前面的文件有关。一种选择是将第一行之后的数据移动到文件的开头。但是对于更大的文件,这将是昂贵的。另请检查:stackoverflow.com/questions/706167/truncate-file-at-front
  • @user1587451 - 文件有多大,目标操作系统是什么,是 windows 吗?立即想到的解决方案是对文件进行内存映射,然后简单地使用 memmove 或 memcpy 将字节移回第一行的长度。虽然这仍然是最严格意义上的复制,但您将利用操作系统来完成它,这将负责几乎所有繁重的工作。如果文件小于 4GB,则可以使用 C 和 WindowsAPI 轻松完成。

标签: c++ fstream getline stdstring boost-interprocess


【解决方案1】:

这是一个用 C 语言为 Windows 编写的解决方案。 它将立即执行并完成一个 700,000 行、245MB 的文件。 (0.14 秒)

基本上,我对文件进行内存映射,以便我可以使用用于原始内存访问的函数来访问内容。一旦文件被映射,我只需使用 strchr 函数来查找用于表示 Windows 中 EOL 的一对符号之一的位置(\n 和 \r) - 这告诉我们第一行的字节长度.

从这里开始,我只是从第二行的第一个字节开始 memcpy 回到内存映射区域的开头(基本上是文件中的第一个字节)。

一旦完成,文件被取消映射,内存映射文件的句柄被关闭,然后我们使用 SetEndOfFile 函数将文件的长度减少第一行的长度。当我们关闭文件时,它已经缩小了这个长度,第一行也不见了。

自从我刚刚创建和写入文件以来,该文件已经在内存中,这显然会在一定程度上改变执行时间,但 Windows 缓存机制是这里的“罪魁祸首”——与我们用来进行操作的机制完全相同很快就完成了。

测试数据是程序源复制100000次保存为testInput2.txt(粘贴10次,全选,复制,粘贴10次-替换原来的10次,一共100次-重复直到输出足够大。我停在这里是因为更多似乎让 Notepad++ 有点不开心)

该程序中几乎不存在错误检查,并且输入不应是 UNICODE,即 - 输入是每个字符 1 个字节。 EOL序列为0x0D,0x0A(\r,\n)

代码:

#include <stdio.h>
#include <windows.h>

void testFunc(const char inputFilename[] )
{
    int lineLength;

    HANDLE fileHandle = CreateFile(
                                    inputFilename,
                                    GENERIC_READ | GENERIC_WRITE,
                                    0,
                                    NULL,
                                    OPEN_EXISTING,
                                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
                                    NULL
                                    );

    if (fileHandle != INVALID_HANDLE_VALUE)
    {
        printf("File opened okay\n");

        DWORD fileSizeHi, fileSizeLo = GetFileSize(fileHandle, &fileSizeHi);

        HANDLE memMappedHandle = CreateFileMapping(
                                                    fileHandle,
                                                    NULL,
                                                    PAGE_READWRITE | SEC_COMMIT,
                                                    0,
                                                    0,
                                                    NULL
                                                );
        if (memMappedHandle)
        {
            printf("File mapping success\n");
            LPVOID memPtr = MapViewOfFile(
                                            memMappedHandle,
                                            FILE_MAP_ALL_ACCESS,
                                            0,
                                            0,
                                            0
                                          );
            if (memPtr != NULL)
            {
                printf("view of file successfully created");
                printf("File size is: 0x%04X%04X\n", fileSizeHi, fileSizeLo);

                LPVOID eolPos = strchr((char*)memPtr, '\r');    // windows EOL sequence is \r\n
                lineLength = (char*)eolPos-(char*)memPtr;
                printf("Length of first line is: %ld\n", lineLength);

                memcpy(memPtr, eolPos+2, fileSizeLo-lineLength);
                UnmapViewOfFile(memPtr);
            }

            CloseHandle(memMappedHandle);
        }
        SetFilePointer(fileHandle, -(lineLength+2), 0, FILE_END);
        SetEndOfFile(fileHandle);
        CloseHandle(fileHandle);
    }
}

int main()
{
    const char inputFilename[] = "testInput2.txt";
    testFunc(inputFilename);
    return 0;
}

【讨论】:

    【解决方案2】:

    确实,你想做的事情并不容易。

    如果你不小心打开同一个文件进行读写,你最终会读到你刚刚写的东西,结果不会是你想要的。

    就地修改文件是可行的:只需打开它,在其中查找,修改并关闭。但是,您想要复制文件的所有内容,但文件开头的 K 字节除外。这意味着您将不得不通过 N 字节的块来迭代地读取和写入整个文件。

    现在完成后,K 字节将保留在需要删除的末尾。我认为没有办法用流来做到这一点。您可以使用 unistd.h 中的 ftruncatetruncate 函数,或者为此使用 Boost.Interprocess truncate

    这是一个例子(没有任何错误检查,我让你添加它):

    #include <iostream>
    #include <fstream>
    #include <unistd.h>
    
    int main()
    {
      std::fstream file;
      file.open("test.txt", std::fstream::in | std::fstream::out);
    
      // First retrieve size of the file
      file.seekg(0, file.end);
      std::streampos endPos = file.tellg();
      file.seekg(0, file.beg);
    
      // Then retrieve size of the first line (a.k.a bufferSize)
      std::string firstLine;
      std::getline(file, firstLine);
    
      // We need two streampos: the read one and the write one
      std::streampos readPos = firstLine.size() + 1;
      std::streampos writePos = 0;
    
      // Read the whole file starting at readPos by chunks of size bufferSize
      std::size_t bufferSize = 256;
      char buffer[bufferSize];
      bool finished = false;
      while(!finished)
      {
        file.seekg(readPos);
        if(readPos + static_cast<std::streampos>(bufferSize) >= endPos)
        {
          bufferSize = endPos - readPos;
          finished = true;
        }
        file.read(buffer, bufferSize);
        file.seekg(writePos);
        file.write(buffer, bufferSize);
        readPos += bufferSize;
        writePos += bufferSize;
      }
      file.close();
    
      // No clean way to truncate streams, use function from unistd.h
      truncate("test.txt", writePos);
      return 0;
    }
    

    我真的很希望能够为文件的就地修改提供更简洁的解决方案,但我不确定是否有。

    【讨论】:

    • 只阅读并删除最后一行会更容易吗?这样的解决方案对我来说也足够了。
    • 对于流,AFAIK,除非你用截断标志打开,否则你不能减小文件的大小,你只能增加它(但我可能错了)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-12
    • 2012-01-04
    • 2022-07-21
    • 1970-01-01
    • 2018-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多