LRU Cache — mi ez, kiszorítási algoritmus és hogyan működik

Szerző: IT Sectr Megjelenés: 2026-06-12 Olvasási idő: 8 perc

LRU Cache (Least Recently Used Cache) — gyorsítótárazási algoritmus, amely kiszorítja a legrégebben nem használt elemeket, amikor a gyorsítótár mérete eléri a határt. Minden olvasáskor vagy íráskor az elem a sor elejére kerül, túlcsorduláskor pedig a végén lévő elem törlődik. A Android Developers (2026) dokumentációja szerint az Android LruCache osztálya LinkedHashMap-et használ access-order módban, és O(1) komplexitást biztosít a get és put műveletekhez.

Főbb pontok

  • LRU Cache — gyorsítótárazási algoritmus, amely a „legrégebben nem használt” elv szerint szorít ki elemeket
  • Komplexitás a get és put műveleteknél — O(1) HashMap + Doubly Linked List implementációval
  • Access-order — minden hozzáféréskor az elem az elejére kerül, a kiszorítás a végéről történik
  • Alkalmazás — képek, hálózati kérések, számítási eredmények és adatbázisadatok gyorsítótárazása
  • Android LruCache — kész implementáció az android.util csomagban, thread-safe és maxSize támogatással

Mi az LRU Cache?

LRU Cache (Least Recently Used Cache) — egy fix méretű adatstruktúra, amely korlátozott számú elemet tárol, és automatikusan eltávolítja azokat, amelyekhez a legritkábban fértek hozzá. Amikor egy alkalmazás lekér egy elemet, az a gyorsítótár „friss” részébe kerül, a régen nem használt elemek pedig a vége felé tolódnak, és a határ elérésekor törlődnek.

A „Least Recently Used” elnevezés a kiszorítási politikát írja le: a tárolt elemek közül a legrégebben nem használt kerül eltávolításra. Ez a lokalitási elven (locality of reference) alapul — a közelmúltban lekért adatok nagy valószínűséggel újra szükségesek lesznek. Ezért tekintik az LRU-t a legtöbb alkalmazás számára az egyik leghatékonyabb gyorsítótárazási stratégiának.

A klasszikus LRU Cache implementáció két adatstruktúrát igényel: egy hash táblát az O(1) hozzáféréshez bármely elemhez kulcs alapján, és egy duplán láncolt listát a használati sorrend nyomon követéséhez. A hash tábla referenciákat tárol a lista csomópontjaira, a lista pedig a legújabb elemtől (fej) a legrégebbiig (farok) tartja a sorrendet.

Az LRU Cache alapműveletei

A get(key) művelet ellenőrzi a kulcs jelenlétét a hash táblában. Ha az elem megtalálható, a lista elejére kerül (a legújabb lesz), és az értéke visszaadásra kerül. Ha nem található — null visszaadása vagy kivétel dobása történik. A put(key, value) művelet új elemet szúr be: ha a kulcs már létezik — az érték frissül és az elem a fejre kerül. Ha a gyorsítótár tele van, a beszúrás előtt a lista farok eleme törlődik. Minden művelet konstans O(1) időben hajtódik végre.

Hogyan működik az LRU Cache

Az LRU Cache algoritmus két elven alapul: egy időrendi hozzáférés-számláló és a túlcsorduláskori kiszorítás mechanikája. Minden elem a duplán láncolt lista egy csomópontjában tárolódik, a mutatók ezekre a csomópontokra — a hash táblában. Minden egyes elemhez való hozzáféréskor az leválasztásra kerül az aktuális pozícióról, és a lista elejére kerül.

Amikor a gyorsítótár mérete eléri a maximális értéket (maxSize), és új elem beszúrására irányuló kérés érkezik, az algoritmus eltávolítja a duplán láncolt lista farok elemét — ez a legrégebben nem használt elem. Az eltávolítás után hely szabadul fel az új elem számára, amely a lista fejére kerül. A hash tábla frissül: a régi kulcs törlődik, az új hozzáadódik.

Az LRU jellemzője — érzéketlenség a ciklikusan ismétlődő hozzáférési mintákra. Ha egy alkalmazás időszakosan a gyorsítótár méreténél nagyobb adathalmazhoz fér hozzá, az LRU thrashing-től szenvedhet — az elemek gyakori cseréjétől, amikor minden új kérés kiszorítja az előzőt. Ilyen forgatókönyvekben az LFU (Least Frequently Used) vagy az adaptív algoritmusok hatékonyabbak lehetnek.

Gyorsítótár mérete és metrikák

Az LRU Cache méretének megválasztása kompromisszum a memóriafogyasztás és a hit-ratio (sikeres hozzáférések százaléka) között. Tipikus értékek mobilalkalmazásokhoz: a rendelkezésre álló memória 10–20%-a képgyorsítótárhoz és 50–200 bejegyzés hálózati válaszgyorsítótárhoz. A hit-ratio 80–95% jó mutatónak számít, amelynél a gyorsítótár indokolja a memóriaköltséget. A monitorozáshoz a hitCount és missCount számlálók használhatók, amelyek elérhetők az Android LruCache implementációjában.

LRU Cache implementáció: HashMap + Doubly Linked List

A kanonikus LRU Cache implementáció egy hash tábla és egy duplán láncolt lista kombinációját használja. A hash tábla O(1) hozzáférést biztosít bármely csomóponthoz kulcs alapján, a duplán láncolt lista pedig — a csomópont fejre helyezését és a farokról való törlését O(1) időben. Fontos, hogy a lista pontosan duplán láncolt legyen: ez lehetővé teszi egy csomópont leválasztását a lista közepéről az összes elem bejárása nélkül.

kotlin
class LruCache<K, V>(
    private val maxSize: Int
) {
    private val map = mutableMapOf<K, Node<V>>()
    private val head = Node<V>(null)
    private val tail = Node<V>(null)

    init {
        head.next = tail
        tail.prev = head
    }

    fun get(key: K): V? {
        val node = map[key] ?: return null
        removeNode(node)
        addToHead(node)
        return node.value
    }

    fun put(key: K, value: V) {
        map[key]?.let { node ->
            removeNode(node)
            node.value = value
            addToHead(node)
            return
        }
        if (map.size >= maxSize) {
            tail.prev?.let { toRemove ->
                removeNode(toRemove)
                removeKeyByValue(toRemove)
            }
        }
        val newNode = Node(value)
        addToHead(newNode)
    }
}

Az implementációban minden csomópont (Node) tárolja az értéket és a referenciákat az előző és következő csomópontra. Az őrszem csomópontok (head és tail) leegyszerűsítik a határeseteket — nincs szükség null ellenőrzésre beszúráskor és törléskor. A get metódus a megtalált csomópontot a fejre helyezi, a put pedig túlcsorduláskor törli a farok elemet. Egy külön removeKeyByValue metódus megkeresi a kulcsot a hash táblában a csomópont referenciája alapján, és eltávolítja azt.

Beépített LruCache implementáció Androidban

Az Android SDK kész LruCache osztályt biztosít az android.util csomagban, amely az LRU algoritmust implementálja LinkedHashMap használatával access-order módban. Az osztály thread-safe, támogatja a hit/miss számlálást, valamint entryRemoved callback-et biztosít az erőforrások felszabadításához elem kiszorításakor. A gyorsítótár mérete tetszőleges egységekben állítható be (bájt, elemek száma) — elegendő felülírni a sizeOf metódust.

LRU Cache vs FIFO és LIFO

Mindhárom algoritmus — LRU, FIFO és LIFO — ugyanazt a feladatot oldja meg: a memóriafogyasztás korlátozását elemek kiszorításával túlcsorduláskor. Azonban alapvetően különböző kritériumokat használnak az áldozat kiválasztásához, ami meghatározza hatékonyságukat különböző forgatókönyvekben.

ParaméterLRUFIFOLIFO
Kiszorítási kritériumLegrégebben nem használtElsőként hozzáadottUtoljára hozzáadott
AdatstruktúraHashMap + Doubly Linked ListSor (Queue)Verem (Stack)
get/put komplexitásO(1)O(1)O(1)
Ellenállás mintáknakMagasKözepesAlacsony
Tipikus alkalmazásKép- és adatgyorsítótárAdatfolyamok puffereléseMűveletek visszavonása (undo)

FIFO a legrégebbi elemet szorítja ki a hozzáadás ideje alapján, függetlenül attól, hogy milyen gyakran fértek hozzá. Ez hatástalan lehet, ha a régi elem még mindig releváns. Az LRU elkerüli ezt a hátrányt a hozzáférési minta figyelembevételével. LIFO a frissen hozzáadott elemet szorítja ki — hasznos az undo forgatókönyvekhez, de nem alkalmas gyorsítótárazásra, mivel az új adatok gyakran szükségesebbek a régebbieknél. Az LRU optimális egyensúlynak tekinthető az implementáció komplexitása és a hit-ratio között a legtöbb alkalmazás számára.

LRU Cache kódpéldák

Vizsgáljuk meg a beépített LruCache osztály használatát az Android SDK-ból betöltött képek gyorsítótárazásához. A példa a gyorsítótár inicializálását mutatja az alkalmazás rendelkezésre álló memóriájának 1/8-án, ami a Google szabványos ajánlása a képek gyorsítótárazásához.

kotlin
import android.util.LruCache

class ImageCache(context: Context) {
    private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
    private val cacheSize = maxMemory / 8

    private val lruCache = object : LruCache<String, Bitmap>(cacheSize) {
        override fun sizeOf(key: String, bitmap: Bitmap): Int {
            return bitmap.rowBytes * bitmap.height / 1024
        }
    }

    fun getBitmap(key: String): Bitmap? {
        return lruCache.get(key)
    }

    fun putBitmap(key: String, bitmap: Bitmap) {
        lruCache.put(key, bitmap)
    }
}

A sizeOf metódus az elem méretét adja vissza ugyanabban a mértékegységben, mint a cacheSize. Itt a Bitmap mérete kilobájtban van használva (rowBytes × height / 1024). Amikor az összes elem sizeOf-jának összege meghaladja a cacheSize-t, az LruCache automatikusan kiszorítja a legrégebben nem használt Bitmap-eket. Az entryRemoved callback használható a bitmap.recycle() meghívásához — memória felszabadítása a kiszorítás előtt.

LRU Cache implementáció Swift-ben

Az iOS nem rendelkezik beépített LRU Cache osztállyal, de könnyen implementálható NSCache segítségével (amely hasonló, de nem dokumentált kiszorítási politikát használ) vagy saját implementációval Dictionary + Doubly Linked List alapokon, ahogy az alábbiakban látható.

swift
class LRUCache<Key: Hashable, Value> {
    private let maxSize: Int
    private var dict = [Key: Node<Value>]()
    private var head: Node<Value>?
    private var tail: Node<Value>?

    init(maxSize: Int) {
        self.maxSize = maxSize
    }

    func get(key: Key) -> Value? {
        guard let node = dict[key] else { return nil }
        moveToHead(node)
        return node.value
    }

    func put(key: Key, value: Value) {
        if let node = dict[key] {
            node.value = value
            moveToHead(node)
            return
        }
        if dict.count >= maxSize {
            tail.map { removeNode($0) }
        }
        let node = Node(value: value)
        dict[key] = node
        addToHead(node)
    }
}

Ebben a Swift implementációban a Node egy belső osztály value, next és prev mezőkkel. A moveToHead metódus leválasztja a csomópontot az aktuális pozícióról, és a lista elejére szúrja be. Túlcsorduláskor a tail — a legrégebben nem használt elem — törlődik. Gyártási verzióhoz ajánlott a szálbiztonság hozzáadása NSLock vagy DispatchQueue segítségével.

Gyakran Ismételt Kérdések

Miben különbözik az LRU Cache az egyszerű HashMap-től?

A HashMap nem rendelkezik méretkorlátozási mechanizmussal — végtelenül nő, amíg a memória el nem fogy. LRU Cache egy kiszorítási politikát ad hozzá (a legrégebben nem használt elemek eltávolítása) a határ elérésekor, ami szükséges az OutOfMemoryError megelőzéséhez korlátozott erőforrású mobilalkalmazásokban.

Hogyan válasszuk ki az LRU Cache méretét képekhez?

A Google azt javasolja, hogy a képek gyorsítótárához az alkalmazás rendelkezésre álló memóriájának 1/8-át különítsük el (Runtime.maxMemory() / 8). Nehéz grafikájú alkalmazásokhoz 1/4-ig megengedett. Vegye figyelembe a lemez gyorsítótárat is (DiskLruCache), amely 2–5-ször több adatot tárolhat a lassabb, de olcsóbb tárolás árán.

Mi a különbség az LRU és az LFU Cache között?

LRU a legrégebben nem használt elemet szorítja ki (az utolsó hozzáférés ideje alapján). LFU a legritkábban használt elemet szorítja ki (a hozzáférés gyakorisága alapján). Az LFU jobb az egyenlőtlen hozzáférési gyakoriságú forgatókönyvekhez, de bonyolultabb implementálni és több memóriát fogyaszt a számlálók tárolásához.

Támogatja az iOS NSCache az LRU politikát?

Az NSCache nem dokumentálja a saját kiszorítási politikáját, de a gyakorlatban egy hibrid megközelítést használ, amely közel áll az LRU-hoz LFU elemekkel. Az NSCache automatikusan kiszorítja az objektumokat memóriahiány esetén, és támogatja a költséget (cost) a priorizáláshoz. Garantált LRU-hoz azonban jobb saját implementációt használni.

Mi az a thrashing az LRU Cache kontextusában?

Thrashing — az az állapot, amikor a gyorsítótár folyamatosan szorít ki és tölt be elemeket valódi haszon nélkül. Akkor fordul elő, amikor az alkalmazás munkadathalmaza nagyobb, mint a gyorsítótár mérete, és az adatokhoz való hozzáférés ciklikus. Megoldás — a gyorsítótár méretének növelése, LFU használata vagy az adaptív ARC (Adaptive Replacement Cache) algoritmus alkalmazása.

Összefoglaló

  • LRU Cache — gyorsítótárazási algoritmus a legrégebben nem használt elemek kiszorításával túlcsorduláskor
  • Komplexitás O(1) get és put esetén HashMap és Doubly Linked List kombinációjával érhető el
  • Access-order — minden kérés az elemet a fejre helyezi, a kiszorítás a lista végéről történik
  • Lokalitás elve — a közelmúltban lekért adatok nagy valószínűséggel újra szükségesek lesznek
  • Hit-ratio 80–95% jó mutatónak számít a legtöbb gyorsítótárazási forgatókönyvhöz
  • LruCache Androidban — kész thread-safe implementáció hit/miss számlálással és callback-ekkel
  • Használja az LRU-t képek, hálózati adatok és számítási eredmények gyorsítótárazására mobilalkalmazásokban

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is