LRU Cache (Least Recently Used Cache) ایک کیشنگ الگورتھم ہے جو ان عناصر کو خارج کر دیتا ہے جو سب سے طویل عرصے سے استعمال نہیں ہوئے جب کیش کا سائز اپنی حد تک پہنچ جاتا ہے۔ ہر پڑھنے یا لکھنے پر، عنصر قطار کے آگے چلا جاتا ہے، اور اوور فلو ہونے پر آخر سے عنصر ہٹا دیا جاتا ہے۔ Android Developers دستاویزات (2026) کے مطابق، Android میں LruCache access-order موڈ میں LinkedHashMap استعمال کرتا ہے اور get اور put آپریشنز کے لیے O(1) پیچیدگی فراہم کرتا ہے۔
اہم نکات
LRU Cache (Least Recently Used Cache) ایک مقررہ سائز کا ڈیٹا ڈھانچہ ہے جو محدود تعداد میں عناصر ذخیرہ کرتا ہے اور خود بخود ان عناصر کو ہٹا دیتا ہے جن تک سب سے کم رسائی حاصل کی گئی ہے۔ جب کوئی ایپلیکیشن کسی عنصر کی درخواست کرتی ہے، تو وہ کیش کے “تازہ” حصے میں چلا جاتا ہے، جبکہ طویل عرصے سے غیر استعمال شدہ عناصر آخر کی طرف کھسک جاتے ہیں اور حد پہنچنے پر ہٹا دیے جاتے ہیں۔
نام “Least Recently Used” اخراج کی پالیسی بیان کرتا ہے: ذخیرہ شدہ تمام عناصر میں سے وہ عنصر ہٹا دیا جاتا ہے جو سب سے طویل عرصے سے استعمال نہیں ہوا۔ یہ حوالہ کی مقامییت (locality of reference) کے مفروضے پر مبنی ہے — حال ہی میں درخواست کردہ ڈیٹا کے دوبارہ ضرورت ہونے کا امکان بہت زیادہ ہے۔ یہی وجہ ہے کہ LRU کو زیادہ تر ایپلیکیشنز کے لیے سب سے مؤثر کیشنگ حکمت عملیوں میں سے ایک سمجھا جاتا ہے۔
کلاسک LRU Cache کے نفاذ کے لیے دو ڈیٹا ڈھانچوں کی ضرورت ہے: کلید کے ذریعے کسی بھی عنصر تک O(1) رسائی کے لیے ایک ہیش ٹیبل اور استعمال کی ترتیب کو ٹریک کرنے کے لیے ایک دوہری منسلک فہرست۔ ہیش ٹیبل فہرست کے نوڈس کے حوالہ جات ذخیرہ کرتی ہے، اور فہرست سب سے نئے عنصر (سر) سے سب سے پرانے (دم) تک ترتیب برقرار رکھتی ہے۔
get(key) آپریشن چیک کرتا ہے کہ کلید ہیش ٹیبل میں موجود ہے یا نہیں۔ اگر عنصر مل جاتا ہے، تو وہ فہرست کے سر پر چلا جاتا ہے (سب سے نیا بن جاتا ہے) اور اس کی قدر واپس کر دی جاتی ہے۔ اگر نہیں ملتا، تو null واپس کیا جاتا ہے یا ایک استثناء پھینکا جاتا ہے۔ put(key, value) آپریشن ایک نیا عنصر داخل کرتا ہے: اگر کلید پہلے سے موجود ہے، تو قدر اپ ڈیٹ ہوتی ہے اور عنصر سر پر چلا جاتا ہے۔ اگر کیش بھرا ہوا ہے، تو داخل کرنے سے پہلے دم کا عنصر ہٹا دیا جاتا ہے۔ تمام آپریشنز مستقل وقت O(1) میں انجام پاتے ہیں۔
LRU Cache الگورتھم دو اصولوں پر مبنی ہے: وقتی ترتیب میں رسائی کی گنتی اور اوور فلو پر اخراج کا طریقہ کار۔ ہر عنصر دوہری منسلک فہرست کے ایک نوڈ میں ذخیرہ ہوتا ہے، اور ان نوڈس کے پوائنٹر ہیش ٹیبل میں رکھے جاتے ہیں۔ ہر رسائی پر، عنصر اپنی موجودہ پوزیشن سے الگ ہو کر فہرست کے سر پر داخل ہو جاتا ہے۔
جب کیش کا سائز اپنی زیادہ سے زیادہ قیمت (maxSize) تک پہنچتا ہے اور ایک نیا عنصر داخل کرنے کی درخواست آتی ہے، تو الگورتھم دوہری منسلک فہرست کے دم کے عنصر کو ہٹا دیتا ہے — یہ سب سے کم حالیہ استعمال شدہ عنصر ہے۔ ہٹانے کے بعد، نئے عنصر کے لیے جگہ خالی ہو جاتی ہے، جو فہرست کے سر پر داخل ہوتا ہے۔ ہیش ٹیبل اس کے مطابق اپ ڈیٹ ہوتی ہے: پرانی کلید ہٹا دی جاتی ہے، نئی شامل کر دی جاتی ہے۔
LRU کی ایک خصوصیت چکری تکرار کے ساتھ رسائی کے نمونوں کے لیے اس کی حساسیت ہے۔ اگر ایپلیکیشن وقتاً فوقتاً کیش کے سائز سے بڑے ڈیٹا سیٹ تک رسائی حاصل کرتی ہے، تو LRU thrashing کا شکار ہو سکتا ہے — بار بار عنصر کی تبدیلی جہاں ہر نئی درخواست پچھلی کو خارج کر دیتی ہے۔ ایسے منظرناموں میں، LFU (Least Frequently Used) یا انکولی الگورتھم زیادہ مؤثر ہو سکتے ہیں۔
LRU Cache کا سائز منتخب کرنا میموری کی کھپت اور ہٹ تناسب (کامیاب رسائیوں کا فیصد) کے درمیان ایک سمجھوتہ ہے۔ موبائل ایپلیکیشنز کے لیے عام قدریں: تصویری کیش کے لیے دستیاب میموری کا 10–20% اور نیٹ ورک رسپانس کیش کے لیے 50–200 اندراجات۔ ہٹ تناسب 80–95% اچھا سمجھا جاتا ہے، جہاں کیش میموری کے اخراجات کو جائز قرار دیتا ہے۔ نگرانی کے لیے، hitCount اور missCount کاؤنٹر استعمال کیے جاتے ہیں، جو Android میں LruCache کے نفاذ میں دستیاب ہیں۔
معیاری LRU Cache کا نفاذ ہیش ٹیبل اور دوہری منسلک فہرست کے امتزاج کا استعمال کرتا ہے۔ ہیش ٹیبل کلید کے ذریعے کسی بھی نوڈ تک O(1) رسائی فراہم کرتی ہے، جبکہ دوہری منسلک فہرست O(1) میں نوڈ کو سر پر منتقل کرنے اور دم سے ہٹانے کی اجازت دیتی ہے۔ اہم بات یہ ہے کہ فہرست دوہری منسلک ہے: یہ تمام عناصر پر iterate کیے بغیر فہرست کے بیچ سے نوڈ کو الگ کرنے کی اجازت دیتی ہے۔
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)
}
}
نفاذ میں، ہر نوڈ (Node) ایک قدر اور پچھلے اور اگلے نوڈس کے حوالہ جات ذخیرہ کرتا ہے۔ سنٹینل نوڈس head اور tail حدی کیسز کو آسان بناتے ہیں — داخل کرنے اور ہٹانے پر null چیک کرنے کی ضرورت نہیں۔ get طریقہ ملے ہوئے نوڈ کو سر پر لے جاتا ہے، اور put اوور فلو پر دم کے عنصر کو ہٹا دیتا ہے۔ ایک علیحدہ طریقہ removeKeyByValue نوڈ کے حوالہ سے ہیش ٹیبل میں کلید ڈھونڈتا ہے اور اسے ہٹا دیتا ہے۔
Android SDK android.util پیکیج میں ایک تیار LruCache کلاس فراہم کرتا ہے، جو access-order موڈ میں LinkedHashMap استعمال کرکے LRU الگورتھم کو نافذ کرتی ہے۔ یہ کلاس thread-safe ہے، hit/miss گنتی کو سپورٹ کرتی ہے، اور عنصر کے اخراج پر وسائل کی صفائی کے لیے entryRemoved کال بیک فراہم کرتی ہے۔ کیش کا سائز صوابدیدی اکائیوں (بائٹس، عناصر کی تعداد) میں سیٹ کیا جاتا ہے — بس sizeOf طریقہ کو اوور رائڈ کریں۔
تینوں الگورتھم — LRU، FIFO اور LIFO — ایک ہی مسئلہ حل کرتے ہیں: اوور فلو پر عناصر کو خارج کرکے میموری کی کھپت کو محدود کرنا۔ تاہم، وہ شکار کو منتخب کرنے کے لیے بنیادی طور پر مختلف معیار استعمال کرتے ہیں، جو مختلف منظرناموں میں ان کی تاثیر کا تعین کرتا ہے۔
| پیرامیٹر | LRU | FIFO | LIFO |
|---|---|---|---|
| اخراج کا معیار | سب سے کم حالیہ استعمال شدہ | پہلے شامل کیا گیا | آخری شامل کیا گیا |
| ڈیٹا ڈھانچہ | HashMap + دوہری منسلک فہرست | قطار (Queue) | اسٹیک (Stack) |
| پیچیدگی get/put | O(1) | O(1) | O(1) |
| پیٹرن برداشت | اعلی | درمیانی | کم |
| عام استعمال | تصویر اور ڈیٹا کیش | اسٹریم بفرنگ | واپسی (undo) |
FIFO سب سے پرانے عنصر کو داخل کرنے کے وقت کے مطابق خارج کرتا ہے، قطع نظر اس کے کہ اس تک کتنی بار رسائی ہوئی۔ یہ غیر موثر ہو سکتا ہے اگر کوئی پرانا عنصر اب بھی متعلقہ ہو۔ LRU رسائی کے پیٹرن پر غور کرکے اس خامی سے بچتا ہے۔ LIFO سب سے حالیہ شامل کردہ عنصر کو خارج کرتا ہے — واپسی کے منظرناموں کے لیے مفید، لیکن کیشنگ کے لیے ناموزوں، کیونکہ نیا ڈیٹا اکثر پرانے سے زیادہ ضروری ہوتا ہے۔ LRU کو زیادہ تر ایپلیکیشنز کے لیے نفاذ کی پیچیدگی اور ہٹ تناسب کے درمیان بہترین توازن سمجھا جاتا ہے۔
آئیے ڈاؤن لوڈ کردہ تصاویر کو کیش کرنے کے لیے Android SDK کی بلٹ ان LruCache کلاس کے استعمال پر غور کریں۔ مثال ایپلیکیشن کی دستیاب میموری کے 1/8 پر کیش کی ابتدا دکھاتی ہے، جو تصویری کیشنگ کے لیے Google کی معیاری سفارش ہے۔
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 طریقہ عنصر کا سائز انہی اکائیوں میں لوٹاتا ہے جن میں cacheSize متعین کیا گیا ہے۔ یہاں، Kilobytes میں Bitmap کا سائز استعمال کیا گیا ہے (rowBytes × height / 1024)۔ جب تمام عناصر کے sizeOf کا مجموعہ cacheSize سے تجاوز کر جاتا ہے، LruCache خود بخود سب سے کم حالیہ استعمال شدہ Bitmap کو خارج کر دیتا ہے۔ entryRemoved کال بیک bitmap.recycle() کالنے کے لیے استعمال کیا جا سکتا ہے — اخراج سے پہلے میموری خالی کرنا۔
iOS میں بلٹ ان LRU Cache کلاس نہیں ہے، لیکن NSCache (جو ایک مشابہ لیکن غیر دستاویزی اخراج پالیسی استعمال کرتا ہے) یا Dictionary + دوہری منسلک فہرست پر مبنی اپنی مرضی کے نفاذ کے ذریعے آسانی سے لاگو کیا جا سکتا ہے، جیسا کہ نیچے دکھایا گیا ہے۔
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)
}
}
اس Swift نفاذ میں، Node value، next اور prev فیلڈز والا ایک اندرونی کلاس ہے۔ moveToHead طریقہ ایک نوڈ کو اس کی موجودہ پوزیشن سے الگ کرتا ہے اور فہرست کے سر پر داخل کرتا ہے۔ اوور فلو پر، دم — سب سے کم حالیہ استعمال شدہ عنصر — ہٹا دیا جاتا ہے۔ پروڈکشن کے لیے، NSLock یا DispatchQueue کے ذریعے تھریڈ سیفٹی شامل کرنے کی سفارش کی جاتی ہے۔
اکثر پوچھے گئے سوالات
HashMap کے پاس سائز محدود کرنے کا کوئی طریقہ کار نہیں ہے — یہ میموری ختم ہونے تک لامحدود بڑھتا رہے گا۔ LRU Cache حد پہنچنے پر ایک اخراج پالیسی (سب سے کم حالیہ استعمال شدہ عناصر کو ہٹانا) شامل کرتا ہے، جو محدود وسائل والی موبائل ایپلیکیشنز میں OutOfMemoryError کو روکنے کے لیے ضروری ہے۔
Google تصویری کیش کے لیے دستیاب میموری کا 1/8 مختص کرنے کی سفارش کرتا ہے (Runtime.maxMemory() / 8)۔ بھاری گرافکس والی ایپلیکیشنز کے لیے، 1/4 تک قابل قبول ہے۔ ڈسک کیش (DiskLruCache) پر بھی غور کریں، جو سست لیکن سستے اسٹوریج کی بدولت 2–5 گنا زیادہ ڈیٹا ذخیرہ کر سکتا ہے۔
LRU اس عنصر کو خارج کرتا ہے جو سب سے طویل عرصے سے استعمال نہیں ہوا (آخری رسائی کے وقت کے مطابق)۔ LFU اس عنصر کو خارج کرتا ہے جو سب سے کم بار استعمال ہوا (رسائی کی تعدد کے مطابق)۔ LFU غیر مساوی رسائی کی تعدد والے منظرناموں کے لیے بہتر ہے، لیکن نفاذ میں زیادہ پیچیدہ ہے اور کاؤنٹر ذخیرہ کرنے کے لیے زیادہ میموری خرچ کرتا ہے۔
NSCache اپنی اخراج پالیسی کو دستاویز نہیں کرتا، لیکن عملی طور پر کچھ LFU عناصر کے ساتھ LRU کے قریب ایک ہائبرڈ طریقہ استعمال کرتا ہے۔ NSCache میموری کم ہونے پر خود بخود اشیاء کو خارج کرتا ہے اور لاگت پر مبنی ترجیح کو سپورٹ کرتا ہے۔ تاہم، ضمانتی LRU رویے کے لیے، اپنی مرضی کے نفاذ کی سفارش کی جاتی ہے۔
Thrashing ایک ایسی حالت ہے جہاں کیش بغیر کسی حقیقی فائدے کے مسلسل عناصر کو خارج اور لوڈ کرتا ہے۔ یہ اس وقت ہوتا ہے جب ایپلیکیشن کا ورکنگ ڈیٹا سیٹ کیش کے سائز سے بڑا ہو اور ڈیٹا تک رسائی چکری ہو۔ حل میں کیش کا سائز بڑھانا، LFU استعمال کرنا، یا انکولی ARC (Adaptive Replacement Cache) الگورتھم لاگو کرنا شامل ہے۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں