Очередь. Логическое описание и физическое представление (файл): долговременное хранение и его цена

Модуль "Очередь и дек" • урок 2 из 7 • ~6 минут

Суть урока. Очередь на файле нужна, когда данные не помещаются в оперативную память или должны переживать перезапуск программы. В классическом Паскале и Enqueue, и Dequeue требуют полного копирования файла; в C добавление ускоряется режимом дозаписи "ab", но извлечение все равно переписывает весь хвост. Надежно, но медленно.
Очередь. Логическое описание и физическое представление (файл): дозапись в конец стоит одной операции записи, а удаление головы переписывает весь хвост во временный файл temp.bin
Дозапись обходится в одну операцию, удаление головы - в копию всего хвоста

Зачем очереди файл

Все реализации из прошлой темы жили в оперативной памяти. Но у ОЗУ два ограничения: объем и энергозависимость. Файловая очередь решает обе проблемы:

Очередь. Логическое описание и физическое представление (файл): отображение на файловый тип

Очередь отображается на файловый тип: file of T в Паскале, FILE* в C. Вспомните урок о файле: классический файл Паскаля открывается либо только на чтение (reset), либо только на запись с очисткой (rewrite). Из-за этого обе операции очереди становятся дорогими:

В C есть послабление: режим "ab" (append binary) дописывает в конец без копирования, поэтому Enqueue дешевеет до одной операции записи. А вот удалить первый элемент файла по-прежнему нельзя иначе, чем переписав весь хвост.

Внешняя память и ее цена

Элементы лежат во внешней памяти - на диске. Операции ввода-вывода на порядки медленнее обращений к ОЗУ, а постоянное копирование файла умножает эту цену на длину очереди. Отсюда вывод: файловая очередь - инструмент надежности, а не скорости.

А как в Python? Файловую очередь "прочитал первую строку - переписал файл без нее" можно собрать и в Python, но на больших объемах она будет так же медленна. На практике для долговременных очередей используют СУБД или брокеры сообщений (RabbitMQ, Redis) - они управляют файловыми буферами эффективно.

Реализация на C

Очередь в файле: код целиком C, 29 строк
#include <stdio.h>

/* Enqueue: в C файл можно открыть на дозапись -
   копировать его целиком не нужно */
void PushFileQueue(const char* filename, int t) {
    FILE* q = fopen(filename, "ab"); /* append binary */
    if (q) {
        fwrite(&t, sizeof(int), 1, q);
        fclose(q);
    }
}

/* Dequeue: удалить голову можно только переписав хвост */
void PopFileQueue(const char* filename) {
    FILE* q = fopen(filename, "rb");
    FILE* temp = fopen("temp.bin", "wb");
    int item;

    if (q && temp) {
        fread(&item, sizeof(int), 1, q); /* пропускаем первый элемент */
        while (fread(&item, sizeof(int), 1, q) == 1) {
            fwrite(&item, sizeof(int), 1, temp); /* копируем остаток */
        }
        fclose(q);
        fclose(temp);
        remove(filename);
        rename("temp.bin", filename);
    }
}

Коротко: главное

  • Файловая очередь нужна для данных больше ОЗУ и для хранения между запусками.
  • В классическом Паскале и Enqueue, и Dequeue копируют весь файл во временный.
  • В C добавление ускоряется режимом дозаписи "ab"; извлечение все равно переписывает хвост.
  • Диск медленнее ОЗУ на порядки, копирование умножает цену на длину очереди.
  • Итог: надежное, но медленное представление - выбор в пользу сохранности данных.

Проверьте себя

Почему в файловой очереди на C операция Dequeue остается дорогой, хотя Enqueue выполняется быстро?

Как вам урок? Спасибо за отзыв!