FIFO Cache (First In First Out Cache) — algoritm de cache în care este eliminat elementul adăugat cel mai devreme, indiferent de cât de des a fost accesat. Se implementează printr-o coadă: elementele noi sunt adăugate la coadă, iar la depășire se elimină elementul din cap. Conform Android Developers (2026), FIFO Cache asigură O(1) pentru toate operațiile, dar este inferior LRU ca hit-ratio la modele de acces neuniforme la date.
Puncte principale
FIFO Cache (First In First Out Cache) — este un cache de dimensiune fixă care utilizează o coadă pentru gestionarea elementelor. Primul element adăugat se află în capul cozii și va fi șters primul la depășire. Elementele noi sunt întotdeauna adăugate la coadă, garantând că ordinea eliminării coincide cu ordinea adăugării.
Spre deosebire de LRU, care reordonează elementele la fiecare acces, FIFO nu schimbă poziția elementelor existente la operațiile get. Acest lucru face algoritmul complet determinist: cunoscând ordinea adăugării, se poate prezice exact ce element va fi eliminat următorul. O astfel de predictibilitate este critică pentru sistemele de timp real, unde trebuie garantată procesarea datelor în ordinea sosirii.
Implementarea FIFO Cache poate fi construită pe mai multe structuri de date: buffer circular (circular buffer) pentru performanță maximă, listă înlănțuită pentru flexibilitate sau două stive (Two-Stack Queue) pentru limbile fără coadă încorporată. Bufferul circular asigură cea mai bună localitate a cache-ului și o supraîncărcare minimă, dar necesită alocarea prealabilă a memoriei pentru maxSize.
Operația enqueue(value) adaugă un element la sfârșitul cozii. Dacă dimensiunea a atins maxSize, înainte de adăugare se elimină elementul din cap. Operația dequeue() elimină și returnează elementul din cap — pentru extragerea forțată a celui mai vechi element. Operația peek() returnează elementul din cap fără eliminare — pentru vizualizarea celui mai vechi element fără a modifica coada.
Algoritmul FIFO imită comportamentul unei cozi obișnuite: primul sosit, primul servit. În contextul cache-ului, aceasta înseamnă că elementul care a stat cel mai mult în cache va fi eliminat la lipsa de spațiu — indiferent de cât de solicitat este. Politica de eliminare FIFO ignoră frecvența accesărilor, ceea ce este atât un punct tare, cât și unul slab al algoritmului.
În implementarea prin buffer circular se utilizează doi pointeri: head (indicele capului cozii) și tail (indicele cozii). La enqueue, elementul este scris la indicele tail, iar tail se incrementează. Dacă tail atinge dimensiunea bufferului, acesta se întoarce la începutul array-ului. Dacă tail ajunge din urmă head — coada este plină și head se deplasează (eliminare). Bufferul circular nu necesită alocare dinamică de memorie și evită fragmentarea.
FIFO Cache demonstrează un hit-ratio de 40% până la 60% pentru sarcini tipice, ceea ce este mai mare decât LIFO, dar mai mic decât LRU. Cu toate acestea, pentru scenariile unde accesul la date este uniform și nu există puncte fierbinți, FIFO poate arăta rezultate comparabile cu LRU la o complexitate de implementare semnificativ mai mică. Memoria este utilizată eficient: nu sunt necesari pointeri suplimentari pentru rearanjarea elementelor.
Principalul dezavantaj al FIFO — susceptibilitatea la poluarea cache-ului (cache pollution). Dacă în cache se adaugă un volum mare de date care nu vor mai fi necesare niciodată, ele vor elimina treptat toate elementele utile, iar hit-ratio va scădea brusc. LRU rezolvă parțial această problemă, deoarece elementele frecvent utilizate se reîmprospătează constant prin mutarea în cap, iar cele unice sunt eliminate mai repede. În FIFO, datele unice rămân în cache până când sunt eliminate în ordinea naturală a cozii.
Alegerea între FIFO, LRU și LIFO depinde de modelul de acces la date și de cerințele de predictibilitate. LRU este optim pentru majoritatea scenariilor, FIFO — pentru date în flux cu acces uniform, LIFO — pentru structuri de stivă.
| Parametru | FIFO | LRU | LIFO |
|---|---|---|---|
| Criteriu de eliminare | Primul adăugat | Cel mai puțin utilizat recent | Ultimul adăugat |
| Structură | Coadă | HashMap + Doubly Linked List | Stivă |
| Predictibilitate | Ridicată | Medie | Ridicată |
| Protecție la poluare | Scăzută | Medie | Scăzută |
| Date în flux | Excelent | Satisfăcător | Slab |
| Resurse (CPU/RAM) | Minim | Mediu | Minim |
FIFO este ideal pentru scenarii unde ordinea procesării trebuie să coincidă cu ordinea sosirii: bufferizarea datelor, logare, procesarea evenimentelor. LRU este mai bun pentru cache cu acces neuniform (date utilizator). LIFO se aplică doar pentru stive și operații Undo. Pentru majoritatea aplicațiilor mobile, LRU rămâne alegerea implicită, dar FIFO poate fi preferat la constrângeri stricte de memorie sau cerințe de predictibilitate.
FIFO Cache își găsește aplicarea în scenarii unde predictibilitatea eliminării sau ordinea procesării datelor contează. Să examinăm principalele cazuri de utilizare.
La redarea audio și video, datele sosesc într-un flux continuu și sunt stocate temporar într-un buffer. FIFO Cache asigură că primele fragmente primite vor fi primele trimise la decodare — aceasta garantează redarea fluentă fără întârzieri. Dimensiunea bufferului se alege în funcție de bitrate-ul fluxului și întârzierea admisibilă: pentru audio tipic 2–5 secunde, pentru video — 10–30 secunde. FIFO este ideal pentru astfel de scenarii, deoarece reordonarea datelor (ca în LRU) nu are sens.
La limitarea numărului de cereri de rețea simultane, FIFO Cache poate fi utilizat pentru stocarea cererilor în așteptare. Prima cerere adăugată va fi executată prima, ceea ce asigură distribuirea echitabilă a resurselor de rețea între diferite componente ale aplicației. Această abordare este utilizată în OkHttp Dispatcher și biblioteci similare pentru gestionarea pool-ului de conexiuni.
Cache-urile simple de răspunsuri HTTP pe dispozitivele mobile folosesc adesea FIFO. Răspunsurile la cereri sunt salvate în ordinea sosirii, iar la atingerea limitei sunt șterse cele mai vechi. Deși LRU ar oferi un hit-ratio mai bun pentru scenariile utilizatorilor, FIFO este mai simplu de implementat și nu necesită stocarea timpului ultimului acces pentru fiecare răspuns. Pentru API-uri cu încărcare uniformă, diferența de hit-ratio între FIFO și LRU este minimă.
În aplicațiile mobile, evenimentele tactile (touch events) sunt bufferizate într-o coadă FIFO înainte de recunoașterea gesturilor. Fiecare eveniment trebuie procesat în ordinea apariției, altfel gestul va fi recunoscut incorect. FIFO Cache cu limitare de dimensiune previne depășirea bufferului la swipe-uri rapide, eliminând cele mai vechi evenimente dacă aplicația nu reușește să le proceseze.
Să examinăm implementarea FIFO Cache în Kotlin folosind bufferul circular — cea mai performantă abordare pentru dispozitive mobile.
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) {
// elimină cel mai vechi element
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]
}
}
Bufferul circular utilizează indicii head și tail, care sunt incrementați ciclic modulo maxSize. Când size == maxSize, enqueue mai întâi elimină elementul de la head (cel mai vechi), deplasează head, apoi scrie noul element la tail. Aritmetica modulară întoarce automat pointerii la începutul array-ului, eliminând copierea manuală a datelor.
În Swift, o alternativă convenabilă — coada FIFO bazată pe două stive (Two-Stack Queue). Toate enqueue sunt efectuate în prima stivă (push), iar la dequeue elementele sunt transferate în a doua stivă în ordine inversă — astfel operația dequeue devine O(1) în medie.
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()
}
}
Două stive asigură o complexitate amortizată O(1) pentru enqueue și dequeue. outStack.removeLast() la eliminare șterge cel mai vechi element (primul adăugat). Această abordare nu necesită alocare prealabilă de memorie, dar poate crea o sarcină suplimentară asupra garbage collector-ului la inversări frecvente ale stivei. Pentru aplicațiile mobile cu memorie limitată, bufferul circular rămâne mai preferabil.
Întrebări frecvente
Coada este o structură de date abstractă fără limită de dimensiune. FIFO Cache este o coadă cu dimensiune maximă fixă și politică de eliminare: la depășire, elementul din cap este șters automat. O coadă obișnuită blochează adăugarea la depășire sau se extinde dinamic, în timp ce FIFO Cache acceptă întotdeauna date noi prin eliminarea celor vechi.
FIFO este mai bun decât LRU în scenariile cu acces uniform la date, unde nu există puncte fierbinți. De exemplu, la cache-ul fișierelor de jurnal sau al datelor în flux, fiecare valoare este utilizată o singură dată, iar LRU nu oferă avantaje. FIFO este, de asemenea, preferat la constrângeri stricte de memorie — nu necesită pointeri suplimentari pentru rearanjări, economisind 16+ octeți per element.
Pe Android se poate utiliza ArrayDeque din biblioteca standard Kotlin, care implementează un buffer circular. Pentru FIFO Cache, înfășurați ArrayDeque: la enqueue verificați dimensiunea și la depășire apelați removeFirst(). Pentru o versiune thread-safe, utilizați ConcurrentLinkedDeque sau SynchronizedArrayDeque.
Dacă în cache se adaugă un volum mare de date utilizate o singură dată, ele vor elimina toate elementele utile. De exemplu, încărcarea a 50 de imagini pentru o galerie la maxSize=30 va elimina primele 20 de imagini utile, deși utilizatorul probabil se va întoarce la ele. LRU rezolvă parțial această problemă: elementele frecvent utilizate se reîmprospătează și rămân în cache.
Da, există algoritmi hibrizi. 2Q (Two-Queue) împarte cache-ul în două părți: fierbinte (LRU) și rece (FIFO). Elementele noi ajung mai întâi în coada FIFO și doar accesările repetate le mută în partea LRU. Acest lucru protejează LRU de poluarea cu date unice, păstrând un hit-ratio ridicat pentru elementele frecvent utilizate.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și