Очередь. Логическое описание и физическое представление (файл): долговременное хранение и его цена
"ab", но извлечение
все равно переписывает весь хвост. Надежно, но медленно.
Зачем очереди файл
Все реализации из прошлой темы жили в оперативной памяти. Но у ОЗУ два ограничения: объем и энергозависимость. Файловая очередь решает обе проблемы:
- размер очереди может превышать объем доступной оперативной памяти;
- данные сохраняются между запусками программы и при сбоях - например, очередь сообщений электронной почты обязана пережить перезагрузку сервера.
Очередь. Логическое описание и физическое представление (файл): отображение на файловый тип
Очередь отображается на файловый тип: file of T в Паскале,
FILE* в C. Вспомните урок о файле:
классический файл Паскаля открывается либо только на чтение
(reset), либо только на запись с очисткой (rewrite).
Из-за этого обе операции очереди становятся дорогими:
- Enqueue: скопировать весь файл во временный, дописать новый элемент в конец, скопировать обратно.
- Dequeue: скопировать файл во временный начиная со второго элемента (пропустив голову), затем заменить исходный.
В C есть послабление: режим "ab" (append binary) дописывает
в конец без копирования, поэтому Enqueue дешевеет до одной операции записи.
А вот удалить первый элемент файла по-прежнему нельзя иначе, чем переписав
весь хвост.
Внешняя память и ее цена
Элементы лежат во внешней памяти - на диске. Операции ввода-вывода на порядки медленнее обращений к ОЗУ, а постоянное копирование файла умножает эту цену на длину очереди. Отсюда вывод: файловая очередь - инструмент надежности, а не скорости.
Реализация на C
Очередь в файле: код целиком
#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 выполняется быстро?