Напишите циркулярный файл на С ++ - PullRequest
8 голосов
/ 20 мая 2009

Мне нужно написать циркуляр на C ++. Программа должна записывать строки в файл, а когда код достигает максимального количества строк, она должна перезаписывать строки в начале файла.

У кого-нибудь есть идеи?

Ответы [ 13 ]

9 голосов
/ 20 мая 2009

К сожалению, вы не можете обрезать / перезаписать строки в начале файла, не переписав все это.

Новое предложение

Я только что подумал о новом подходе, который может помочь вам ...

Вы можете добавить к вашему файлу небольшой заголовок, имеющий следующую структуру.

Редактировать: Мусор, я только что описал вариант кольцевого буфера !

Поля заголовка

  • 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.

Не очень простой алгоритм, я полностью признаю! Тем не менее, я думаю, что это довольно элегантно. Дайте мне знать, если что-то из этого не ясно, конечно. Надеюсь, он должен делать именно то, что вы хотите сейчас.

Оригинальное предложение

Теперь, если вам гарантировано, что строки имеют постоянную длину (в байтах), вы можете достаточно просто вернуться к подходящей точке и перезаписать существующие данные. Однако это может показаться маловероятной ситуацией. Если вы не возражаете против наложения ограничения на максимальную длину ваших строк и дополнительного дополнения каждой строки, которую вы пишете, до этой максимальной длины, то это может облегчить вам задачу. Тем не менее, у него есть свои недостатки, такие как значительное увеличение размера файла при определенных обстоятельствах (то есть большинство строк значительно короче, чем максимальная длина.) Все зависит от ситуации, является ли это приемлемым или нет ...

Наконец, вместо этого вы можете захотеть взглянуть на использование существующей системы ведения журнала, в зависимости от ваших конкретных целей.

7 голосов
/ 20 мая 2009

Обычный способ обработки журналирования, который не увеличивается в размерах, состоит в том, чтобы использовать прокручиваемые файлы журналов, и катить их один раз в день или аналогично, и сохранять только N последних файлов.

Например, каждый день вы создаете новый лог-файл с именем `application_2009_05_20.log 'и начинаете писать в него, всегда добавляя.

Как только у вас есть 14-дневные файлы журналов, вы начинаете удалять самые старые.

5 голосов
/ 20 мая 2009

Поскольку файлы ориентированы на байты, и вам нужен сервис, ориентированный на строки, у вас есть два варианта:

  1. реализовать строковую оболочку для файла

  2. переключиться на какое-либо линейно-ориентированное устройство. Просто на мой взгляд: в SQLite есть несколько хороших оболочек C ++.

2 голосов
/ 20 мая 2009

Используйте кольцевой буфер и запишите буфер в файл для каждого добавления.

Вот небольшое и простое решение по размеру кода. Это простой циклический буфер строк, и каждый раз, когда вы добавляете строки, он записывает весь буфер строк в файл (конечно, вы несете значительную стоимость записи всех строк для одиночная операция добавления. Так что это подходит только для небольшого числа строк).

Простая реализация кольцевого буфера с выводом в файл:

// 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...
1 голос
/ 20 мая 2009

Просто создайте отображение файла требуемого размера (CreateFileMapping или mmap), запишите строки в буфер и начните заново, когда будет достигнуто максимальное число.

1 голос
/ 20 мая 2009

Вы можете использовать log4cxx с RollingFileAppender для записи этой информации в файл журнала. RollingFileAppender будет обрабатывать пролистывание файла журнала при достижении определенного размера. Я не думаю, что это точно то, что вы хотите, но это довольно просто - возможно, это подойдет.

1 голос
/ 20 мая 2009

если файлы должны быть текстовыми:
Это очень проблематично с различной длиной строки. Первые две строки по 80 символов в каждой. Как перезаписать их строкой из 100 символов?

Если новая строка должна заменить первую строку , это приведет к вставке файла, что является очень дорогой операцией (в основном весь остальной файл должен быть прочитан и записан). Вы действительно не хотите делать это для всех, кроме минимального количества данных.

Если это для целей ведения журнала, используйте файлы журнала прокрутки - например, один день (как подсказывает lassevek). Я сделал это еще проще: когда размер файла превышает лимит, старый файл переименовывается в .bak (старый .bak удаляется) и запускается заново. При ограничении в 1 МБ это сохраняет, например, последние 1 МБ, но не занимающие более 2 МБ.

Вы можете использовать аналогичный механизм с двумя или более файлами. По сути, переместите «ролловер» в файлы, а не в строки.

, если файл может быть в проприетарном формате:
Используйте базовый механизм БД (например, SQLite, как предлагается) или другой механизм структурированного хранения.

1 голос
/ 20 мая 2009

Простое решение:

  1. Есть какой-то разделитель для линий.
  2. Каждый раз, когда вы добавляете новую строку, просто перезаписывайте весь текст, начиная с текущей строки, до тех пор, пока он не достигнет разделителя.
  3. Конец файла является особым случаем и может иметь некоторые отступы для сохранения постоянного размера файла.

Это решение предназначено для обеспечения постоянной длины файла, а не постоянного числа строк в файле. Количество строк будет меняться со временем в зависимости от длины. Это решение затрудняет быстрый поиск определенных номеров строк, хотя вы можете прикрепить некоторые данные индикатора вверху или внизу файла, чтобы упростить это.

«Умное» решение (вариант решения выше):

Просто используйте тот же трюк, который иногда используется для deques. Просто точно переходите от начала файла к концу, но следите за тем, где находится начало / конец файла. Вы можете написать утилиту для развертывания, чтобы преобразовать этот файл в стандартный, если вы хотите прочитать его с помощью программы, которая его не поддерживает. Это решение ДЕЙСТВИТЕЛЬНО легко реализовать, но мне больше нравится версия выше.

Уродливое решение:

При добавлении строк добавляйте умеренное количество отступов к каждой добавляемой строке.

Каждый раз, когда вы хотите добавить новую строку, выполните следующие действия:

  1. Определить длину текущей строки, включая отступы. Обратите внимание, что начало текущей строки равно концу, не включая заполнение, предыдущей строки.
  2. Если текущая строка достаточно длинна, чтобы уместиться в пределах строки, на которой вы находитесь, вставьте ее. Добавьте левый отступ к концу предыдущей строки, равный 1/3 любого лишнего пробела, и правый, равный 2 / 3 лишних места.
  3. Если текущая строка недостаточно длинна, чтобы вписаться в линию, на которой вы находитесь, сдвигайте линии впереди вас (съедая их отступы), пока они не станут свободными.
  4. Если на шаге 3 достигнут некоторый порог, переписать весь файл с дополнительным заполнением.

Обратите внимание, что это будет работать очень плохо, если ваши строки не соответствуют длине. Более простое решение состоит в том, чтобы гарантировать, что строки имеют постоянную длину (но, в некотором смысле, можно создать многострочные «линии» в случае превышения этой длины.

0 голосов
/ 20 мая 2009

Чтобы обойти вещь переменного размера, вы, вероятно, в конечном итоге получите косвенное обращение и схему распределения. Это будет состоять из блока косвенного обращения с фиксированным количеством «указателей» в файле и одного «следующего за записываемым» указателя, который будет обернут вокруг N.

Но основной трюк был бы в добавлении косвенности.

0 голосов
/ 20 мая 2009

Если вы хотите сгенерировать этот файл для ввода в другое приложение, я думаю, что вам лучше всего войти непосредственно в базу данных отношений (SQL Server, MySQL и т. Д.), А затем периодически генерировать этот файл по мере зарегистрированные данные.

...