代码之家  ›  专栏  ›  技术社区  ›  mxcl

从STL文件中删除除最后500000字节之外的所有字节

  •  1
  • mxcl  · 技术社区  · 17 年前

    我们的日志类在初始化时会将日志文件截断为500000字节。从那时起,日志语句将附加到文件中。

    我们这样做是为了保持低磁盘使用率,我们是一种商品终端用户产品。

    显然,保留前500000个字节是没有用的,所以我们保留最后500000个字节。

    我们的解决方案存在一些严重的性能问题。做这件事的有效方法是什么?

    8 回复  |  直到 17 年前
        1
  •  6
  •   user43922    17 年前

    我可能会创建一个新文件,在旧文件中查找,从旧文件到新文件进行缓冲读/写,将新文件重命名为旧文件

    我认为你最好简单地:

    #include <fstream>
    std::ifstream ifs("logfile");  //One call to start it all. . .
    ifs.seekg(-512000, std::ios_base::end);  // One call to find it. . .
    char tmpBuffer[512000];
    ifs.read(tmpBuffer, 512000);  //One call to read it all. . .
    ifs.close();
    std::ofstream ofs("logfile", ios::trunc);
    ofs.write(tmpBuffer, 512000); //And to the FS bind it.
    

    通过简单地将最后512K复制到缓冲区,以截断模式打开日志文件(清除日志文件的内容),并将相同的512K吐回文件开头,可以避免文件重命名。

    请注意,上述代码尚未经过测试,但我认为这个想法应该是合理的。

    您可以将512K加载到内存中的缓冲区中,关闭输入流,然后打开输出流;这样,您就不需要两个文件,因为您可以输入、关闭、打开、输出512个字节,然后执行。这样可以避免重命名/文件重定位的魔法。

    如果你在某种程度上不讨厌将C与C++混合使用,你也可以:

    (注意:伪代码;我记不起我脑子里的mmap调用了)

    int myfd = open("mylog", O_RDONLY); // Grab a file descriptor
    (char *) myptr = mmap(mylog, myfd, filesize - 512000) // mmap the last 512K
    std::string mystr(myptr, 512000) // pull 512K from our mmap'd buffer and load it directly into the std::string
    munmap(mylog, 512000); //Unmap the file
    close(myfd); // Close the file descriptor
    

    取决于很多事情,mmap 可以是 比寻找更快。如果你好奇的话,在谷歌上搜索“fseek vs mmap”会得到一些有趣的阅读材料。

    人酪氨酸羟化酶

        2
  •  3
  •   Steve Jessop    17 年前

    我可能会:

    • 创建一个新文件。
    • 在旧文件中查找。
    • 从旧文件到新文件进行缓冲读/写。
    • 将新文件重命名为旧文件。

    要执行前三个步骤(省略错误检查,例如,如果文件小于500k大,我不记得seekg会做什么):

    #include <fstream>
    
    std::ifstream ifs("logfile");
    ifs.seekg(-500*1000, std::ios_base::end);
    std::ofstream ofs("logfile.new");
    ofs << ifs.rdbuf();
    

    然后我认为你必须做一些非标准的事情来重命名文件。

    不过,显然你需要500k的可用磁盘空间才能工作,所以如果你截断日志文件的原因是因为它刚刚填满了磁盘,这是不好的。

    我不知道为什么搜索速度很慢,所以我可能错过了什么。我不认为查找时间取决于文件的大小。可能取决于文件的是,我不确定这些函数是否在32位系统上处理2GB以上的文件。

    如果复制本身很慢,那么根据平台的不同,您可以通过使用更大的缓冲区来加速它,因为这减少了系统调用的次数,更重要的是,磁盘头必须在读取点和写入点之间进行搜索的次数。为此,请执行以下操作:

    const int bufsize = 64*1024; // or whatever
    std::vector<char> buf(bufsize);
    ...
    ifs.rdbuf()->pubsetbuf(&buf[0], bufsize);
    

    用不同的值进行测试,然后查看。你也可以尝试增加ofstream的缓冲区,我不确定这是否会有所不同。

    请注意,在“实时”日志文件上使用我的方法很麻烦。例如,如果在副本和重命名之间附加了一个日志条目,那么你将永远丢失它,并且你试图替换的文件上的任何打开的句柄都可能导致问题(在Windows上它会失败,在linux上它会替换文件,但旧的仍将占用空间,并且在句柄关闭之前仍会被写入)。

    如果截断是从执行所有日志记录的同一线程完成的,那么就没有问题,你可以保持简单。否则,您将需要使用锁或其他方法。

    这是否完全稳健取决于平台和文件系统:移动和替换可能是也可能不是原子操作,但通常不是,因此您可能必须先重命名旧文件,然后重命名新文件,然后删除旧文件,并进行错误恢复,启动时检测是否有重命名的旧文件,如果有,将其放回并重新启动截断。STL不能帮助您处理平台差异,但有boost::filesystem。

    很抱歉,这里有很多警告,但很大程度上取决于平台。如果你在PC上,那么我很困惑为什么复制区区半兆的数据需要任何时间。

        3
  •  3
  •   MSalters    17 年前

    如果你碰巧使用窗户,不要费心四处复制零件。只需通过调用以下命令告诉Windows您不再需要第一个字节 FSCTL_SET_SPARSE 和 FSCTL_SET_ZERO_DATA

        4
  •  1
  •   Svante    17 年前

    如果您可以在重新初始化之间生成几GB的日志文件,那么仅在初始化时截断该文件似乎并没有什么帮助。

    我想我会尝试提出一种专门的文本文件格式,以便始终替换原地的内容,并用指向“当前”行的指针环绕。您需要一个恒定的线宽来分配一次磁盘空间,并将指针放在该文件的第一行或最后一行。

    这样,文件就永远不会增长或收缩,你总是会得到最后一个 N 条目。

    插图 N =6(“|”表示空格填充到那里):

    #myapp logfile, lines = 6, width = 80, pointer = 4                              |
    [2008-12-01 15:23] foo bakes a cake                                             |
    [2008-12-01 16:15] foo has completed baking a cake                              |
    [2008-12-01 16:16] foo eats the cake                                            |
    [2008-12-01 16:17] foo tells bar: I have made you a cake, but I have eaten it   |
    [2008-12-01 13:53] bar would like some cake                                     |
    [2008-12-01 14:42] bar tells foo: sudo bake me a cake                           |
        5
  •  1
  •   EvilTeach    17 年前

    另一种解决方案是让日志类检测日志文件大小何时超过500k,并打开一个新的日志文件,然后关闭旧的日志文件。

    然后,日志类将查看旧文件,并删除最旧的文件

    记录器将有两个配置参数。

    1. 500k作为开始新日志的阈值
    2. 要保留的旧原木的数量。

    这样,日志文件管理将是一个自我维护的东西。

        6
  •  0
  •   Klathzazt    17 年前

    所以你想要文件的结尾——你把它复制到某种缓冲区来做什么?你是什么意思“把它写回”文件。你的意思是它覆盖了文件,在init上截断为原始文件的500k字节+它添加了什么?

    建议:

    • 重新思考你在做什么。如果这有效并且是所期望的,那么它有什么问题呢?为什么要改变?是否存在性能问题?你是否开始想知道你所有的日志条目都去了哪里?对于这类问题来说,提供更多的问题比发布现有的行为更有帮助。没有人能完全评论这一点,除非他们知道完整的问题——因为这是主观的。

    • 如果是我,我的任务是重新设计你的日志记录机制,我会内置一个机制,将日志文件截断为:时间长度或大小。

        7
  •  0
  •   gbjbaanb    17 年前

    我认为这与计算机无关,但你们是如何写日志课的。我觉得很奇怪,你把最后500k读成一个字符串,你为什么要这样做?

    只需追加到日志文件中。

      fstream myfile;
      myfile.open("test.txt",ios::app);
    
        8
  •  0
  •   Max Lybbert    10 年前

    Widefinder 2 has a lot of talk about efficient IO available (或者,更准确地说,“注释”列下的链接有很多关于高效IO的信息)。

    回答您的问题:

    1. (标题)使用[标准库]从文件中删除前500000个字节

    在文件系统操作方面,标准库有点有限。如果你不局限于标准库,你可以很容易地提前结束一个文件(也就是说,“这一点之后的所有内容都不再是这个文件的一部分”),但很难延迟开始一个文件。

    只需在文件中查找500000字节,然后启动一个新文件的缓冲副本,这将是有效的。但是一旦你做到了这一点,标准库就没有现成的“重命名此文件”功能。原生操作系统函数可以有效地重命名文件,Boost也是如此。文件系统或STLSoft。

    1. (实际问题)我们的日志类在初始化时,会在文件结束前找到500000字节,并将其余部分复制到 std::string 然后将其写回文件。

    在这种情况下,您将删除文件的最后一位,这在标准库之外很容易做到。只需使用文件系统操作将文件大小设置为500000字节(例如。, ftruncate , SetEndOfFile ).之后的任何事情都将被忽略。