用c ++写一个循环文件

时间:2009-05-20 12:44:59

标签: c++ file-io ofstream

我需要用c ++编写一个循环文件​​。程序必须在文件中写入行,当代码达到最大行数时,它必须覆盖文件开头的行。

有人有任何想法吗?

13 个答案:

答案 0 :(得分:9)

不幸的是,你不能在不重写整个文件的情况下截断/覆盖文件开头的行。

新建议

我刚刚想到了一种新的方法,可以帮到你......

您可以在文件中包含一个具有以下结构的小标题。

编辑:垃圾,我刚刚描述了circular buffer的变体!

标题字段

  • Bytes 00 - 07 (long) - 写入文件的总行数(当前)。
  • Bytes 08 - 15 (long) - 指向文件“实际”第一行开头的指针。这最初将是标题结束后的字节,但稍后会在数据被覆盖时更改。
  • Bytes 16 - 23 (long) - 文件“结束部分”的长度。同样,这最初将为零,但稍后会在数据被覆盖时更改。

读取算法(伪代码)

读取整个文件。

Read the header field that points to the start of the "actual" first line
Read the header field that specifies the length of the "end section"
Read every line until the end of the file
Seek to the byte just after the end of the header
Read every line until the "end section" has been fully read

写入算法(伪代码)

将任意数量的新行写入文件。

Read the header field that contains the total no. of lines in the file
If (line count) + (no. of new lines) <= (maximum no. of lines) Then
    Append new lines to end of file
    Increment header field for line count by (no. of ne lines)
Else
    Append as many lines as possible (up to maximum) to end of file
    Beginning at pointer to first line (in header field), read as many lines as still need to be written
    Find the total byte count of the lines just read
    Set the header field that points to the first line to the next byte in the stream
    Keep writing the new lines to the end of the file, each at a time, until the byte count of the remaining lines is less than the byte count of the lines at the beginning of the file (it may be that this condition is true immediately, in which case you don't need to write any more)
    Write the remaining new lines to the start of the file (starting at the byte after the header)
    Set the header field that contains the length of the "end section" of the file to the number of bytes just written after the header.

我完全承认,这不是一个非常简单的算法!尽管如此,我认为它在某种程度上相当优雅。当然,如果有任何不清楚的地方,请告诉我。希望它应该完全符合您的要求。

原始建议

现在,如果您保证行的长度不变(以字节为单位),您可以轻松地找回适当的点并覆盖现有数据。然而,这似乎是一种不太可能的情况。如果您不介意强制限制您的线条必须具有最大长度,并且另外填充您写入的每条线条的最大长度,那么这对您来说很容易。但是,它有一些缺点,例如在某些情况下大大增加文件大小(即大多数行都比最大长度短得多)。这一切都取决于情况是否可以接受......

最后,您可能希望考虑使用现有的日志记录系统,具体取决于您的确切目的。

答案 1 :(得分:7)

处理不会大小爆炸的日志记录的常用方法是使用滚动日志文件,每天滚动一次或类似文件,并且只保留N个最新文件。

例如,每天都会创建一个文件名为“application_2009_05_20.log”的新日志文件,并开始写入,始终附加。

一旦有14天的日志文件,就开始删除最旧的日志文件。

答案 2 :(得分:5)

由于文件是面向字节的,并且您需要面向行的服务,因此您有两种选择:

  1. 在文件

  2. 周围实现面向行的包装器
  3. 切换到某些面向行的设备。最重要的是:SQLite有一些不错的C ++包装器。

答案 3 :(得分:2)

使用循环缓冲区并将缓冲区写入每个添加的文件。

这是一个小而简单的代码大小的解决方案。它是一个简单的循环字符串缓冲区,每次添加字符串时,它都会将整个字符串缓冲区写入文件中(当然,为了编写 all ,会产生重要成本单个添加操作的字符串。所以这只适用于少量字符串)。

简单实现循环缓冲区并输出到文件:

// GLOBALS ( final implementation should not use globals )
#define MAX_CHARS_PER_LINE (1024)
#define MAX_ITEMS_IN_CIRCULARBUF (4) // must be power of two
char    lineCircBuf[MAX_ITEMS_IN_CIRCULARBUF][MAX_CHARS_PER_LINE];
int     lineCircBuf_add = 0;
int     lineCircBuf_rmv = 0; // not being used right now
uint32_t lineCircBuf_mask = MAX_ITEMS_IN_CIRCULARBUF-1;
char    FILENAME[] = "lineCircBuf.txt";
FILE *  ofp = NULL;

int addLine(char * str) {
    int i;

    // Error checking
    if( strlen(str) > MAX_CHARS_PER_LINE ) {
        return -1; // failure
    }
    if( ofp != NULL) {
        fclose(ofp);
    }

    // Copy string into circular buffer
    strncpy( &(lineCircBuf[lineCircBuf_add][0]),
             str,
             MAX_CHARS_PER_LINE );
    lineCircBuf_add = ( lineCircBuf_add + 1 ) & lineCircBuf_mask;

    // Write to file
    ofp = fopen(FILENAME,"w");
    for( i = 0; i < MAX_ITEMS_IN_CIRCULARBUF-1; i++ ) {
        fprintf( ofp, "%s\n", lineCircBuf[i] );
    }
    fprintf( ofp, "%s", lineCircBuf[i] ); // do not add a newline to the last line b/c we only want N lines in the file

    return 0; // success
}

int removeLine(int index) {
    // not implemented yet
}

void unitTest() {
    int i;

    // Dummy text to demonstrate adding string lines
    char lines[5][MAX_CHARS_PER_LINE] = {
        "Hello world.",
        "Hello world AGAIN.",
        "The world is interesting so far!",
        "The world is not interesting anymore...",
        "Goodbye world."
    };

    // Add lines to circular buffer
    for( i = 0; i < sizeof(lines)/sizeof(lines[0]); i++ ) {
        addLine(&(lines[i][0]));
    }
}

int main() {
    unitTest();
    return 0;
}

所以在上面的例子中我们有5行输入,我们的缓冲区只有4行。因此输出应该只有4行,第一行应该被最后一行“Goodbye world”覆盖。当然,输出的第一行确认了“再见世界”:

Goodbye world.
Hello world AGAIN.
The world is interesting so far!
The world is not interesting anymore...

答案 4 :(得分:1)

简单的解决方案:

  1. 为行提供某种分隔符。
  2. 每次添加新行时,只需覆盖当前行中的所有文本,直到达到分隔符为止。
  3. 文件的结尾是一种特殊情况,可能有一些填充以保持文件大小不变。
  4. 此解决方案旨在提供恒定的文件长度,而不是文件中的常量行数。线的数量会随着时间的变化而变化,具体取决于长度。此解决方案使得快速查找特定行号变得更加困难,但您可以在文件的顶部或底部粘贴一些指标数据,以使这更容易。

    “聪明”解决方案(上述解决方案的变体):

    使用有时用于deques的相同技巧。只是从文件的开头到结尾显着地环绕,但是跟踪文件的开头/结尾的位置。当您希望使用不支持它的程序读取该文件时,您可以编写一个解包实用程序来将此文件转换为标准文件。这个解决方案真的很容易实现,但我更喜欢上面的版本。

    丑陋的解决方案:

    添加线条时,请为添加的每一行添加适量的填充。

    每次添加新行时,请执行以下操作:

    1. 确定当前行的长度,包括填充。请注意,当前行的开头等于前一行的结尾,不包括填充。
    2. 如果当前行足够长以适合您所在的行,请将其放入。将左边距填充添加到前一行的末尾,等于任何多余空间的1/3,右边填充等于2 /任何多余空间中的3个。
    3. 如果当前行不够长,不适合你所在的行,请在你前面换行(吃他们的填充物)直到他们的房间。
    4. 如果步骤3达到某种阈值,则用更多填充重写整个文件。
    5. 请注意,除非您的线条长度非常一致,否则这种方法效果会非常差。一个更简单的解决方案是保证线条具有恒定的长度(但是如果超过该长度,则以某种方式设置多线“线”。

答案 5 :(得分:1)

如果文件需要是文本文件:
对于不同的线长度,这是非常有问题的。前两行各有80个字符,如何用100个字符的行覆盖它?

如果新行替换第一行,这将导致文件插入,这是一个非常昂贵的操作(基本上,需要读取和写入文件的整个剩余部分) 。除了极少量的数据外,你真的不希望这样做。

如果这是用于记录目的,请使用rollng日志文件 - 例如每天一次(按照lassevek的建议)。 我做得更简单:当文件大小超过限制时,旧文件重命名为.bak(旧的.bak被删除),然后重新开始。限制为1MB,这样可以保留最后1 MB,但从不占用超过2 MB。

您可以使用包含两个或更多文件的类似机制。基本上,将“翻转”移动到文件,而不是行。

如果文件可能采用专有格式:
使用基本数据库引擎(如建议的SQLite)或其他结构化存储机制。

答案 6 :(得分:1)

您可以使用log4cxxRollingFileAppender将此信息写入日志文件。当RollingFileAppender达到一定大小时,{{1}}将处理日志文件的滚动。我认为它不是完全你想要什么,但它相当简单 - 也许它会这样做。

答案 7 :(得分:1)

只需创建所需大小的文件(CreateFileMapping或mmap)的映射,在缓冲区中写入行,并在达到最大数量时重新开始。

答案 8 :(得分:0)

这将是棘手的,因为文件I / O使用字节作为存储的基础单元,而不是行。

我的意思是你可以把fseek()回到开头并破坏早期的数据,但我有预感并不是你想要的。

答案 9 :(得分:0)

我已经通过在某处保留文件的当前写入位置来完成此操作。当您需要添加一条线时,您将寻找位置,写入线并以原子方式更新位置。如果溢出,则在写入行之前寻求归零。我们今天为大小受限的循环日志文件执行此操作。在线约束的基础上进行它有点奇怪,但可能以类似的方式完成。我们的写循环看起来像:

logFile.lockForWrite();
currentPosition = logFile.getWritePosition();
logFile.seek(currentPosition);
for each line in lineBuffer {
    if ((currentPosition+line.length()) > logFile.getMaxSize()) {
        currentPosition = 0;
        logFile.seek(0);
    }
    logFile.write(line);
    currentPosition += line.length();
}
logFile.setWritePosition(currentPosition);
logFile.unlock();

棘手的部分是维护当前写入位置并找到一些方法来协调读取文件(例如,使用tail实用程序),同时应用程序正在写入它。您的阅读器实用程序也必须跟踪写入位置,因此它的读取循环变为:

lastPosition = logFile.getWritePosition();
while (!killed) {
    logFile.wait();
    logFile.lockForRead();
    newPosition = logFile.getWritePosition();
    logFile.seek(lastPosition);
    newLine = logFile.readFrom(lastPosition, (newPosition-lastPosition));
    lastPosition = newPosition;
    logFile.unlock();
}

这不是任何特定的语言 - 它只是伪代码,但这个想法就在那里。当然,我把所有有趣的边缘情况都留给了读者。

所有这些都说......我同意其他意见。除非你有充分的理由,否则不要这样做。这听起来是个好主意,但是:

  • 实施难以写入
  • 提高效率更难
  • 由于写入位置必须在某处维护,因此多个实用程序必须就如何读取,更新,初始化等达成一致。
  • 使用非线性日志可以使用greptailperl等现有工具进行日志处理。

总的来说,最好使用一些现有的软件包日志包来实现可配置的日志文件管理。请查看Apache's log4cxxPoco's Poco::Logger

答案 10 :(得分:0)

简易解决方法:

  • 每行定义最多80个字符的长度。将更长的“线”包裹成几行。
  • 在一行中添加一个行标题。例如“[#589]这是第589行”,所以你会知道最先出现的等等。

答案 11 :(得分:0)

如果您想要生成此文件以输入到另一个应用程序,我认为您最好的选择是直接登录到关系数据库(SQL Server,MySQL,等等......)然后根据需要定期生成该文件记录的数据。

答案 12 :(得分:0)

为了解决变量大小的问题,你可能最终得到一个间接和分配方案。这将包含一个间接块,其中包含固定数量的“指针”,以及一个“下一个要写入”的指针,它将环绕N.

但主要的伎俩是添加间接。