FIFO Cache (First In First Out Cache) — це алгоритм кешування, який витісняє елемент, доданий раніше за всіх, незалежно від того, як часто до нього зверталися. Реалізується через чергу: нові елементи додаються в хвіст, а при переповненні видаляється елемент з голови. За даними Android Developers (2026), FIFO Cache забезпечує O(1) для всіх операцій, але поступається LRU за hit-ratio при нерівномірних патернах доступу до даних.
Головне
FIFO Cache (First In First Out Cache) — це кеш фіксованого розміру, що використовує чергу для керування елементами. Перший доданий елемент опиняється в голові черги і буде видалений першим при настанні переповнення. Нові елементи завжди додаються в хвіст, гарантуючи, що порядок витіснення збігається з порядком додавання.
На відміну від LRU, який перевпорядковує елементи при кожному зверненні, FIFO не змінює позицію існуючих елементів при запитах get. Це робить алгоритм повністю детермінованим: знаючи порядок додавання, можна точно передбачити, який елемент буде витіснено наступним. Така передбачуваність критична для систем реального часу, де потрібно гарантувати обробку даних у порядку надходження.
Реалізація FIFO Cache може бути побудована на кількох структурах даних: кільцевий буфер (circular buffer) для максимальної продуктивності, зв'язний список для гнучкості або два стеки (Two-Stack Queue) для мов без вбудованої черги. Кільцевий буфер забезпечує найкращу кеш-локальність і мінімальний оверхед, але вимагає попереднього виділення пам'яті під maxSize.
Операція enqueue(value) додає елемент у хвіст черги. Якщо розмір досяг maxSize, перед додаванням видаляється елемент з голови. Операція dequeue() видаляє та повертає елемент з голови — для примусового вилучення найстарішого елемента. Операція peek() повертає головний елемент без видалення — для перегляду найстарішого елемента без зміни черги.
Алгоритм FIFO імітує поведінку звичайної черги: перший увійшов обслуговується першим. У контексті кешування це означає, що елемент, який пробув у кеші найдовше, буде видалений при нестачі місця — незалежно від того, наскільки він затребуваний. Політика витіснення FIFO ігнорує частоту звернень, що є одночасно сильною та слабкою стороною алгоритму.
При реалізації через кільцевий буфер використовуються два покажчики: head (індекс голови черги) та tail (індекс хвоста). При enqueue елемент записується за індексом tail, і tail збільшується. Якщо tail досягає розміру буфера, він «загортається» до початку масиву. Якщо tail наздоганяє head — черга заповнена, і head зсувається (витіснення). Кільцевий буфер не вимагає динамічного виділення пам'яті та уникає фрагментації.
FIFO Cache демонструє hit-ratio від 40% до 60% для типових навантажень, що вище, ніж у LIFO, але нижче, ніж у LRU. Однак для сценаріїв, де доступ до даних рівномірний і не має «гарячих» точок, FIFO може показувати результати, порівняні з LRU, при значно меншій складності реалізації. Пам'ять витрачається ефективно: не потрібні додаткові покажчики для перестановок елементів.
Основний недолік FIFO — схильність до забруднення кешу (cache pollution). Якщо в кеш додати великий обсяг даних, які більше ніколи не знадобляться, вони поступово витіснять усі корисні елементи, і hit-ratio різко впаде. LRU частково вирішує цю проблему, оскільки часто використовувані елементи будуть постійно «освіжатися» переміщенням у голову, а однократні — витіснятися швидше. У FIFO однократні дані залишаються в кеші доти, доки не витісняться природним порядком черги.
Вибір між FIFO, LRU та LIFO залежить від патерну доступу до даних та вимог до передбачуваності поведінки. LRU оптимальний для більшості сценаріїв, FIFO — для потокових даних з рівномірним доступом, LIFO — для стекових структур.
| Параметр | FIFO | LRU | LIFO |
|---|---|---|---|
| Критерій витіснення | Перший доданий | Найменш нещодавно використаний | Останній доданий |
| Структура | Черга | HashMap + Doubly Linked List | Стек |
| Передбачуваність | Висока | Середня | Висока |
| Захист від забруднення | Низька | Середня | Низька |
| Потокові дані | Відмінно | Задовільно | Погано |
| Ресурси (CPU/RAM) | Мінімум | Середньо | Мінімум |
FIFO ідеальний для сценаріїв, де порядок обробки повинен збігатися з порядком надходження: буферизація даних, логування, обробка подій. LRU кращий для кешування з нерівномірним доступом (користувацькі дані). LIFO застосовний тільки для стеків та Undo. Для більшості мобільних додатків LRU залишається вибором за замовчуванням, але FIFO може бути кращим при жорстких обмеженнях пам'яті або вимогах до передбачуваності.
FIFO Cache знаходить застосування в сценаріях, де важлива передбачуваність витіснення або порядок обробки даних має значення. Розглянемо основні випадки використання.
При відтворенні аудіо та відео дані надходять безперервним потоком і тимчасово зберігаються в буфері. FIFO Cache забезпечує, що перші отримані фрагменти будуть першими відправлені на декодування — це гарантує плавне відтворення без затримок. Розмір буфера вибирається виходячи з бітрейту потоку та допустимої затримки: для аудіо типічно 2–5 секунд, для відео — 10–30 секунд. FIFO ідеальний для таких сценаріїв, оскільки перевпорядкування даних (як у LRU) не має сенсу.
При обмеженні кількості одночасних мережевих запитів FIFO Cache може використовуватися для зберігання очікуючих запитів. Перший доданий запит буде виконаний першим, що забезпечує чесний розподіл ресурсів мережі між різними компонентами додатку. Такий підхід застосовується в OkHttp Dispatcher та аналогічних бібліотеках для керування пулом з'єднань.
Прості кеші HTTP-відповідей на мобільних пристроях часто використовують FIFO. Відповіді на запити зберігаються в порядку надходження, а при досягненні ліміту видаляються найстаріші. Хоча LRU дав би кращу hit-ratio для користувацьких сценаріїв, FIFO простіший у реалізації та не вимагає зберігання часу останнього доступу для кожної відповіді. Для API з рівномірним навантаженням різниця в hit-ratio між FIFO та LRU мінімальна.
У мобільних додатках події дотиків (touch events) буферизуються в FIFO-черзі перед обробкою жестів. Кожна подія повинна бути оброблена в порядку виникнення, інакше жест буде розпізнано невірно. FIFO Cache з обмеженням розміру запобігає переповненню буфера при швидких свайпах, відкидаючи найстаріші події, якщо додаток не встигає їх обробити.
Розглянемо реалізацію FIFO Cache на Kotlin з використанням кільцевого буфера — найбільш продуктивного підходу для мобільних пристроїв.
class FifoCache<V>(
private val maxSize: Int
) {
private val buffer = arrayOfNulls<V>(maxSize)
private var head = 0
private var tail = 0
private var size = 0
fun enqueue(value: V) {
if (size == maxSize) {
// витіснити найстаріший елемент
buffer[head] = null
head = (head + 1) % maxSize
size--
}
buffer[tail] = value
tail = (tail + 1) % maxSize
size++
}
fun dequeue(): V? {
if (size == 0) return null
val result = buffer[head]
buffer[head] = null
head = (head + 1) % maxSize
size--
return result
}
fun peek(): V? {
return buffer[head]
}
}
Кільцевий буфер використовує індекси head та tail, які циклічно інкрементуються за модулем maxSize. Коли size == maxSize, enqueue спочатку видаляє елемент за head (найстаріший), зсуває head, а потім записує новий елемент за tail. Модульна арифметика автоматично «загортає» покажчики до початку масиву, виключаючи ручне копіювання даних.
У Swift зручна альтернатива — FIFO черга на основі двох стеків (Two-Stack Queue). Всі enqueue виконуються в перший стек (push), а при dequeue елементи переносяться в другий стек у зворотному порядку — так операція dequeue стає O(1) у середньому.
struct FifoCache<Value> {
private let maxSize: Int
private var inStack = [Value]()
private var outStack = [Value]()
mutating func enqueue(value: Value) {
if inStack.count + outStack.count >= maxSize {
if outStack.isEmpty {
outStack = inStack.reversed()
inStack.removeAll()
}
outStack.removeLast()
}
inStack.append(value)
}
mutating func dequeue() -> Value? {
if outStack.isEmpty {
outStack = inStack.reversed()
inStack.removeAll()
}
return outStack.popLast()
}
}
Два стеки забезпечують амортизовану складність O(1) для enqueue та dequeue. outStack.removeLast() при витісненні видаляє найстаріший елемент (перший доданий). Цей підхід не вимагає попереднього виділення пам'яті, але може створювати додаткове навантаження на збирач сміття при частих реверсах стеку. Для мобільних додатків з обмеженою пам'яттю кільцевий буфер залишається більш кращим.
Часті запитання
Черга — це абстрактна структура даних без обмеження розміру. FIFO Cache — це черга з фіксованим максимальним розміром і політикою витіснення: при переповненні елемент з голови видаляється автоматично. Звичайна черга блокує додавання при переповненні або розширюється динамічно, тоді як FIFO Cache завжди приймає нові дані за рахунок витіснення старих.
FIFO кращий за LRU в сценаріях з рівномірним доступом до даних, де немає «гарячих» точок. Наприклад, при кешуванні лог-файлів або потокових даних кожне значення використовується один раз, і LRU не дає переваги. FIFO також кращий при жорстких обмеженнях по пам'яті — він не вимагає додаткових покажчиків для перестановок, економлячи 16+ байт на елемент.
На Android можна використовувати ArrayDeque зі стандартної бібліотеки Kotlin, який реалізує кільцевий буфер. Для FIFO Cache оберніть ArrayDeque: при enqueue перевіряйте розмір і при перевищенні викликайте removeFirst(). Для thread-safe версії використовуйте ConcurrentLinkedDeque або SynchronizedArrayDeque.
Якщо в кеш додати великий обсяг однократно використовуваних даних, вони витіснять всі корисні елементи. Наприклад, завантаження 50 зображень для галереї при maxSize=30 витіснить перші 20 корисних зображень, хоча користувач імовірно повернеться до них. LRU частково вирішує цю проблему: часто використовувані елементи «освіжаються» і залишаються в кеші.
Так, існують гібридні алгоритми. 2Q (Two-Queue) розділяє кеш на дві частини: гарячу (LRU) та холодну (FIFO). Нові елементи спочатку потрапляють у FIFO-чергу, і лише повторні звернення переміщують їх у LRU-частину. Це захищає LRU від забруднення однократними даними, зберігаючи високий hit-ratio для часто використовуваних елементів.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.