在c++中替换,添加和删除巨大文本文件中的行

Replacing, adding and deleting lines in huge text file in C++

本文关键字:文本 巨大 文件 删除 c++ 替换 添加      更新时间:2023-10-16

我有一些大的文本文件(>1Gb),需要替换或删除一些行。我需要能够替换一个随机选择的行与另一个,删除它或插入一行又一行。

我尝试使用getline(file, line)计算行数-这需要太多时间。这也会导致需要很长时间才能到达一行。

是否有更有效或更好的方法来做到这一点?

您必须考虑到,为了更改一行,您需要读取整个文件,搜索该行,更改它,然后写入整个文件。这确实很糟糕,但文件本质上是连续的,如果不访问所有之前的文件,您就无法到达一个地方。

根据您的实际问题,您基本上有两个选择:

1)如果您对文件重复执行此操作,则可以通过使用更高级的数据结构对其进行优化。基本上,您存储的不再是平面文本文件,而是行集合。这可以通过为每行添加一个带有偏移量的头文件,一个包含所有更改(当然,在读取时必须考虑)的附加增量文件来实现,这些更改仅在它开始变大或操作完成时应用,或者甚至将所有行保留在更传统的DBMS中。

2)如果这个操作很少在每个文件中执行,你可能希望优化一下你的读例程。你可能有最好的机会mmap整个文件,并扫描它为EOL自己,因为你可以摆脱一大堆的内存分配/字符串副本这种方式。虽然mmap显然会在后台造成内存压力,但我发现这种技术在实践中相当快,并且非常容易实现。

我认为您可以使用数据库来保存每一行。表的每一行可以有两列(ID和行)。在对数据库进行归档之后,您可以向数据库请求具有随机ID的行。

我认为,在您的情况下,最好的选择是使用内存映射文件。CreateFileMapping和MapViewOfFile将会帮助你。

下面是一个在Windows中使用内存映射文件的简单示例。我省略了错误检查代码,并将任务简化为将随机字符串的字符替换为'?'。这样做是为了只概述主要方面:

1)创建一个内存映射文件(CreateFile -> CreateFileMapping-> MapViewOfFile)

2)处理它,因为它只是一个内存块。是的,如果你需要在行模式下处理,你必须从一开始就扫描它,但我认为映射它将是最快的方法。

重要提示:现实生活中的任务可能需要对原始文件进行缩小或扩展。减少它非常容易——只需将相应的部分移回并在关闭时截断文件。扩展需要更复杂的技术。

3)不要忘记使用UnmapViewOfFile/CloseHandle来释放资源。

#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR fileName[] = _T("SomeHugeFile.dat");
    HANDLE hFile = CreateFile(
        fileName,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );
    if (!hFile) {
        // Process the error
    }
    DWORD fileSize = GetFileSize(hFile, NULL);
    if (!fileSize) {
        // Check if it's an actual errro with GetLastError
        // and process accordingly if so
    }
    HANDLE hMemMappedFile = CreateFileMapping(
        hFile,
        NULL,
        PAGE_READWRITE,
        0,
        0,
        NULL
        );
    if (!hMemMappedFile) {
        // Process the error
    }
    LPVOID mappedMemory = MapViewOfFile(
        hMemMappedFile,
        FILE_MAP_ALL_ACCESS,
        0,
        0,
        0   // To the end of the file
        );

    DWORD lineToDelete = 3; // Some random line number
    // Assuming the file is ASCII and with Unix line endings
    char *mappedMemoryStart = (char *)mappedMemory;
    char *mappedMemoryEnd = mappedMemoryStart + fileSize;
    char *lineStart = NULL;
    char *lineEnd = NULL;
    // Find the line start:
    DWORD lineNumber = 0;
    for (lineStart = (char *)mappedMemory; lineStart < mappedMemoryEnd; lineStart++) {
        if (*lineStart == 'n') {
            lineNumber++;
            if (lineNumber == lineToDelete) {
                lineStart++;
                break;
            }
        }
    }
    if (lineStart >= mappedMemoryEnd) {
        // Error: has gone beyond file end
    }
    for (lineEnd = lineStart; lineEnd < mappedMemoryEnd; lineEnd++) {
        if (*lineEnd == 'n') {
            break;
        }
    }
    // Now mangle the found line:
    while (lineStart < lineEnd) {
        *lineStart = '?';
        lineStart++;
    }
    UnmapViewOfFile(mappedMemory);
    CloseHandle(hMemMappedFile);
    CloseHandle(hFile);
    return 0;
}