【问题标题】:Replacing, adding and deleting lines in huge text file in C++ [closed]在 C++ 中替换、添加和删除巨大文本文件中的行 [关闭]
【发布时间】:2015-06-05 19:43:42
【问题描述】:

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

我尝试使用getline(file, line) 计算行数 - 这需要太多时间。这也导致通过它的号码到达一行的时间很长。

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

【问题讨论】:

  • 在这种特殊情况下,我不会相信库函数。逐个字符获取并根据需要使用内存。当然,您无法在内存中获得整行(多 GB),您需要一些技巧取决于您的问题规范。告诉我们更多关于你的目标以及你到目前为止所做的事情。
  • 每行字符数是否保证相同?如果是这样,那么在不使用getline 的情况下搜索特定行的任务会变得更加容易。问题在于何时将文件与丢失的行重新放在一起。我认为你不能快速做到这一点。
  • 字符数不一样,随便一个文件即可。而且行数也不知道。所以主要问题是交换两行随机行或从文件中删除一行。到目前为止我没有做太多,只是尝试到达最后一行并使用“getline”计算总行数。
  • 您可以按照建议的答案使用内存映射文件。如果您不能使用内存映射文件,另一种选择是硬核。以二进制模式读取文本文件,可能一次读取几兆字节。每兆字节,搜索 cr-lf 并保持计数,直到到达该行。使用 C 风格的字符串搜索函数,例如 strchr。我的个人经验 - 以这种方式做事,至少在我使用的硬件上,只需几秒钟就可以在数千兆字节的文本文件中找到一行,而不是反复调用getline。同样,YMMV 取决于硬件、磁盘访问等。

标签: c++ windows


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    您基本上有两种选择,具体取决于您的实际问题:

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

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

    【讨论】:

    • mmap 的链接适用于 Linux,是否适用于 windows?
    • 我认为您应该在询问之前进行搜索。 stackoverflow.com/questions/4087282/…
    • 对于选项 1,将文本文件与单独的索引文件配对。您的索引文件有一个标题,然后是描述每一行的固定长度记录(偏移量和索引)。第一次处理文件时,您仍然必须解析每个文本行(使用内存映射文件),但是您在执行此操作时编写索引文件,因此您不必再次解析行。当然,如果您进行操作,请保持两者同步。这对于包含多个小型二进制文件(图形)的 blob 文件很常见,但适用于文本行。
    【解决方案3】:

    我认为您可以使用数据库来保存每一行。表格的每一行可以有两列(ID 和行)。归档数据库后,您可以向数据库询问带有随机 ID 的行。

    【讨论】:

    • 我不认为将多个 GB 数据复制到数据库中是完成此任务的好方法。然后他应该将所有内容都保存在数据库上,而不是文件上。
    • 出于这个原因,我认为最好在数据库上维护所有内容。他可以轻松地更改他想要的每一行。当然,他必须删除文本文件并只保留 db
    【解决方案4】:

    我相信,在您的情况下,最好的选择是使用内存映射文件。 CreateFileMappingMapViewOfFile 将帮助您解决此问题。

    以下是在 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;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-11
      • 2011-10-20
      • 2014-02-05
      • 1970-01-01
      • 2013-09-04
      • 1970-01-01
      • 2010-09-14
      • 2020-01-01
      • 2014-01-01
      相关资源
      最近更新 更多