LRU Cache — bu nədir, sıxışdırma alqoritmi və necə işləyir

Müəllif: IT Sectr Dərc olunub: 2026-06-12 Oxuma vaxtı: 8 dəq

LRU Cache (Least Recently Used Cache) — keş ölçüsü limitə çatdıqda ən uzun müddət istifadə olunmayan elementləri sıxışdıran keşləmə alqoritmi. Hər oxuma və ya yazma zamanı element növbənin əvvəlinə köçürülür, daşma zamanı isə sondakı element silinir. Android Developers (2026) sənədlərinə əsasən, Android-də LruCache access-order rejimində LinkedHashMap istifadə edir və get və put əməliyyatları üçün O(1) mürəkkəbliyini təmin edir.

Əsas Məqamlar

  • LRU Cache — elementləri “ən az istifadə olunmuş” prinsipi ilə sıxışdıran keşləmə alqoritmi
  • Mürəkkəblik get və put əməliyyatları — HashMap + Doubly Linked List vasitəsilə O(1)
  • Access-order — hər müraciətdə element əvvələ köçürülür, sıxışdırma sondan edilir
  • Tətbiq — şəkillərin, şəbəkə sorğularının, hesablama nəticələrinin və verilənlər bazası məlumatlarının keşlənməsi
  • Android LruCache — android.util paketində hazır implementasiya, thread-safe və maxSize dəstəyi ilə

LRU Cache nədir?

LRU Cache (Least Recently Used Cache) — sabit ölçülü məlumat strukturu olub, məhdud sayda elementi saxlayır və ən az müraciət edilənləri avtomatik silir. Tətbiq element tələb etdikdə, o keşin “təzə” hissəsinə köçürülür, uzun müddət istifadə olunmayan elementlər isə sona sürüşərək limit çatdıqda silinir.

“Least Recently Used” adı sıxışdırma siyasətini təsvir edir: saxlanılanlar arasında ən uzun müddət istifadə olunmayan element silinir. Bu, müraciət lokalizasiyası (locality of reference) fərziyyəsinə əsaslanır — yaxın vaxtda tələb edilmiş məlumatların təkrardan ehtiyac olma ehtimalı yüksəkdir. Məhz buna görə LRU əksər tətbiqlər üçün ən effektiv keşləmə strategiyalarından biri hesab olunur.

Klassik LRU Cache implementasiyası iki məlumat strukturu tələb edir: açara görə istənilən elementə O(1) çıxış üçün hash cədvəli və istifadə sırasını izləmək üçün ikiqat əlaqəli siyahı. Hash cədvəli siyahının düyünlərinə istinadlar saxlayır, siyahı isə ən yeni elementdən (baş) ən kökə (quyruq) qədər sıranı qoruyur.

LRU Cache-in əsas əməliyyatları

get(key) əməliyyatı hash cədvəlində açarın mövcudluğunu yoxlayır. Element tapılarsa, siyahının başına köçürülür (ən yeni olur) və dəyəri qaytarılır. Tapılmazsa — null qaytarılır veya istisna atılır. put(key, value) əməliyyatı yeni element daxil edir: açar artıq mövcuddursa — dəyər yenilənir və element başa köçürülür. Keş doludursa, daxil etmədən əvvəl siyahının quyruq elementi silinir. Bütün əməliyyatlar sabit O(1) vaxtında yerinə yetirilir.

LRU Cache necə işləyir

LRU Cache alqoritmi iki prinsipə əsaslanır: zamana görə müraciət sayğacı və daşma zamanı sıxışdırma mexanikası. Hər element ikiqat əlaqəli siyahının düyünündə saxlanılır, bu düyünlərə göstəricilər isə hash cədvəlindədir. Elementə hər müraciətdə o cari mövqeyindən ayrılaraq siyahının əvvəlinə daxil edilir.

Keşin ölçüsü maksimum dəyərə (maxSize) çatdıqda və yeni element daxil etmək sorğusu gəldikdə, alqoritm ikiqat əlaqəli siyahının quyruq elementini silir — bu ən uzun müddət istifadə olunmayan elementdir. Silindikdən sonra yeni element üçün yer boşalır və o siyahının başına daxil edilir. Hash cədvəli də yenilənir: köhnə açar silinir, yenisi əlavə olunur.

LRU-nun xüsusiyyəti — tsiklik təkrarlanan giriş nümunələrinə həssas olmamasıdır. Tətbiq keş ölçüsündən böyük məlumat dəstinə dövri olaraq müraciət edirsə, LRU thrashingdən — hər yeni sorğunun əvvəlkini sıxışdırdığı tez-tez dəyişmədən əziyyət çəkə bilər. Belə ssenarilərdə LFU (Least Frequently Used) və ya adaptiv alqoritmlər daha effektiv ola bilər.

Keş ölçüsü və metrikalar

LRU Cache ölçüsünün seçimi yaddaş istehlakı ilə hit-ratio (uğurlu müraciətlərin faizi) arasında kompromisdir. Mobil tətbiqlər üçün tipik dəyərlər: şəkil keşi üçün mövcud yaddaşın 10–20%-i və şəbəkə cavab keşi üçün 50–200 giriş. Hit-ratio 80–95% yaxşı göstərici hesab olunur, bu zaman keş yaddaş xərclərini əsaslandırır. Monitorinq üçün Android-də LruCache implementasiyasında mövcud olan hitCount və missCount sayğaclarından istifadə olunur.

LRU Cache-in implementasiyası: HashMap + Doubly Linked List

Kanonik LRU Cache implementasiyası hash cədvəli və ikiqat əlaqəli siyahının kombinasiyasından istifadə edir. Hash cədvəli açara görə istənilən düyünə O(1) çıxış təmin edir, ikiqat əlaqəli siyahı isə düyünü başa köçürməyi və sondan silməyi O(1) ilə yerinə yetirir. Siyahının məhz ikiqat əlaqəli olması vacibdir: bu, bütün elementləri gəzmədən düyünü siyahının ortasından ayırmağa imkan verir.

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)
    }
}

Implementasiyada hər düyün (Node) dəyər və əvvəlki və növbəti düyünlərə istinadlar saxlayır. Baş və quyruq sentinel-düyünləri sərhəd hallarını sadələşdirir — daxil etmə və silmə zamanı null yoxlaması tələb olunmur. Get metodu tapılmış düyünü başa köçürür, put isə daşma zamanı quyruq elementini silir. Ayrıca removeKeyByValue metodu hash cədvəlində düyün istinadına görə açarı tapır və onu silir.

Android-də daxili LruCache implementasiyası

Android SDK android.util paketində hazır LruCache sinfi təqdim edir ki, o da access-order rejimində LinkedHashMap istifadə edərək LRU alqoritmini həyata keçirir. Sinif thread-safe-dir, hit/miss hesablamasını dəstəkləyir, həmçinin element sıxışdırıldıqda resursları azad etmək üçün entryRemoved callbackini təmin edir. Keş ölçüsü ixtiyari vahidlərdə (bayt, element sayı) təyin edilir — sizeOf metodunu override etmək kifayətdir.

LRU Cache vs FIFO və LIFO

Hər üç alqoritm — LRU, FIFO və LIFO — eyni məsələni həll edir: daşma zamanı elementləri sıxışdırmaqla yaddaş istehlakını məhdudlaşdırmaq. Lakin onlar qurban seçimi üçün prinsipial fərqli meyarlardan istifadə edir ki, bu da müxtəlif ssenarilərdə effektivliyini müəyyənləşdirir.

ParametrLRUFIFOLIFO
Sıxışdırma meyarıƏn az istifadə olunmuşİlk daxil edilmişSon daxil edilmiş
Məlumat strukturuHashMap + Doubly Linked ListNövbə (Queue)Yığın (Stack)
get/put mürəkkəbliyiO(1)O(1)O(1)
Nümunələrə davamlılıqYüksəkOrtaAşağı
Tipik tətbiqŞəkil və məlumat keşiAxın buferləşdirilməsiƏməliyyatların geri alınması (undo)

FIFO istifadə tezliyindən asılı olmayaraq ən köhnə elementi vaxtına görə sıxışdırır. Bu, köhnə element hələ də aktualdırsa, səmərəsiz ola bilər. LRU bu çatışmazlıqdan qaçır, müraciət nümunəsini nəzərə alır. LIFO təzə daxil edilmiş elementi sıxışdırır — undo ssenariləri üçün faydalıdır, lakin keşləmə üçün yararsızdır, çünki yeni məlumatlar çox vaxt köhnələrdən daha çox lazım olur. LRU əksər tətbiqlər üçün implementasiya mürəkkəbliyi ilə hit-ratio arasında optimal balans hesab olunur.

LRU Cache kod nümunələri

Yüklənmiş şəkillərin keşlənməsi üçün Android SDK-dan daxili LruCache sinfinin istifadəsini nəzərdən keçirək. Nümunə Google-un şəkil keşi üçün standart tövsiyəsi olan tətbiqin mövcud yaddaşının 1/8 hissəsində keşin inisializasiyasını göstərir.

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)
    }
}

sizeOf metodu elementin ölçüsünü cacheSize ilə eyni vahidlərdə qaytarır. Burada Bitmap ölçüsü kilobaytlarla istifadə olunur (rowBytes × height / 1024). Bütün elementlərin sizeOf cəmi cacheSize-i keçdikdə, LruCache avtomatik olaraq ən az istifadə olunan Bitmap-ları sıxışdırır. entryRemoved callback-i bitmap.recycle() çağırmaq üçün istifadə oluna bilər — sıxışdırmadan əvvəl yaddaşı azad etmək.

Swift-də LRU Cache implementasiyası

iOS-da daxili LRU Cache sinfi yoxdur, lakin onu NSCache vasitəsilə (oxşar, lakin sənədləşdirilməmiş sıxışdırma siyasətindən istifadə edir) və ya Dictionary + Doubly Linked List əsasında öz implementasiyanızla asanlıqla həyata keçirmək olar, aşağıda göstərildiyi kimi.

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)
    }
}

Bu Swift implementasiyasında Node — value, next və prev sahələri olan daxili sinifdir. moveToHead metodu düyünü cari mövqeyindən ayıraraq siyahının başına daxil edir. Daşma zamanı ən az istifadə olunmuş element olan tail silinir. Production versiyası üçün NSLock və ya DispatchQueue növbəsi vasitəsilə thread təhlükəsizliyinin əlavə edilməsi tövsiyə olunur.

Tez-tez verilən suallar

LRU Cache adi HashMap-dən nə ilə fərqlənir?

HashMap ölçü məhdudiyyəti mexanizminə malik deyil — yaddaş tükənənə qədər sonsuz böyüyəcək. LRU Cache limit çatdıqda sıxışdırma siyasəti (ən az istifadə olunmuş elementlərin silinməsi) əlavə edir ki, bu da məhdud resurslu mobil tətbiqlərdə OutOfMemoryError-un qarşısını almaq üçün vacibdir.

Şəkillər üçün LRU Cache ölçüsünü necə seçmək olar?

Google şəkil keşi üçün tətbiqin mövcud yaddaşının 1/8 hissəsini ayırmağı tövsiyə edir (Runtime.maxMemory() / 8). Ağır qrafikaya malik tətbiqlər üçün 1/4-ə qədər icazə verilir. Həmçinin DiskLruCache disk keşini də nəzərə alın ki, o daha yavaş, lakin ucuz yaddaş hesabına 2–5 dəfə çox məlumat saxlaya bilər.

LRU və LFU Cache arasında nə fərq var?

LRU ən uzun müddət istifadə olunmayan elementi sıxışdırır (son müraciət vaxtına görə). LFU ən az istifadə olunmuş elementi sıxışdırır (müraciət tezliyinə görə). LFU qeyri-bərabər giriş tezliyi olan ssenarilər üçün daha yaxşıdır, lakin implementasiyası daha mürəkkəbdir və sayğacları saxlamaq üçün daha çox yaddaş sərf edir.

iOS-də NSCache LRU siyasətini dəstəkləyir?

NSCache öz sıxışdırma siyasətini sənədləşdirmir, lakin praktikada LRU-ya yaxın elementləri olan hibrid yanaşmadan istifadə edir. NSCache yaddaş çatışmazlığında avtomatik olaraq obyektləri sıxışdırır və prioritetləşdirmə üçün dəyəri (cost) dəstəkləyir. Lakin zəmanətli LRU üçün öz implementasiyanızdan istifadə etmək daha yaxşıdır.

LRU Cache kontekstində thrashing nədir?

Thrashing — keşin real fayda vermədən daim elementləri sıxışdırıb yüklədiyi vəziyyət. Tətbiqin işçi məlumat dəsti keş ölçüsündən böyük olduqda və məlumatlara giriş tsiklik olduqda yaranır. Həll yolu — keş ölçüsünü artırmaq, LFU istifadə etmək və ya adaptiv ARC (Adaptive Replacement Cache) alqoritmini tətbiq etməkdir.

Nəticə

  • LRU Cache — daşma zamanı ən az istifadə olunmuş elementləri sıxışdıran keşləmə alqoritmi
  • Mürəkkəblik O(1) get və put üçün HashMap və Doubly Linked List kombinasiyası ilə əldə edilir
  • Access-order — hər sorğu elementi başa köçürür, sıxışdırma siyahının sonundan aparılır
  • Lokallıq prinsipi — yaxın vaxtda tələb edilmiş məlumatların təkrardan lazım olma ehtimalı yüksəkdir
  • Hit-ratio 80–95% əksər keşləmə ssenariləri üçün yaxşı göstərici hesab olunur
  • LruCache Android-də — hit/miss hesablaması və callback-lərlə hazır thread-safe implementasiya
  • İstifadə edin LRU-nu mobil tətbiqlərdə şəkillərin, şəbəkə məlumatlarının və hesablama nəticələrinin keşlənməsi üçün

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun