Android LruCache 原理

Android LruCache 原理

LruCache是基于Least Recently Used(最近最少使用)算法实现的一个线程安全的数据缓存类,当超出设定的缓存容量时,优先淘汰最近最少使用的数据
LruCache的LRU缓存策略是利用LinkedHashMap来实现的,并通过封装get/put等相关方法来实现控制缓存大小以及淘汰元素,但不支持为null的key和value

因此先学习LinkedHashMap 原理才能更有助于理解本文

使用方法

创建LruCache前先要定义缓存大小,然后在构造函数中传入缓存大小,并重写sizeOf方法计算value的大小
使用LruCache缓存Bitmap的典型使用方法如下:

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    // 获取可用内存的最大值,超出这个值会抛出OutOfMemory
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    // 使用最大可用内存的1/8作为缓存大小,以KB为单位
    int cacheSize = maxMemory / 8;
    // LruCache通过构造函数传入缓存值
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // 重写sizeOf方法来计算图片大小
            return bitmap.getAllocationByteCount() / 1024;
        }
    };
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    mMemoryCache.put(key, bitmap);
}
public Bitmap getBitmapFromCache(String key) {
    return mMemoryCache.get(key);
}

初始化

LruCache只有一个构造函数,传入的参数就是LruCache的缓存大小,然后创建一个基于操作顺序排序的LinkedHashMap

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    // 设定缓存大小
    this.maxSize = maxSize;
    // LinkedHashMap accessOrder参数传递true
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

默认sizeOf函数返回1表示每个value大小是1

protected int sizeOf(K key, V value) {
   return 1;
}

如有特殊需求比如缓存Bitmap则需要重写sizeOf方法,并返回bitmap占用的内存大小

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        // 重写sizeOf方法来计算图片大小
        return bitmap.getAllocationByteCount() / 1024;
    }
};

put

put方法首先会做判空处理,因此不能保存空的键值对,通过加锁实现线程安全,每次put数据后都会计算容量

public final V put(K key, V value) {
     // 不支持为null的key和value
     if (key == null || value == null) {
         throw new NullPointerException("key == null || value == null");
     }

     V previous;
     // 加锁保证线程安全
     synchronized (this) {
         putCount++;
         // 计算键值对的大小后累加
         size += safeSizeOf(key, value);
         // 保存键值对,返回被替换的值,若不是替换则返回null
         previous = map.put(key, value);
         if (previous != null) {
             // 减去被替换的键值对大小
             size -= safeSizeOf(key, previous);
         }
     }

     if (previous != null) {
         // 方法默认为空,可重写做资源回收工作
         entryRemoved(false, key, previous, value);
     }

     // 根据最大容量判断是否需要淘汰不常用的数据
     trimToSize(maxSize);
     return previous;
 }

trimToSize方法会不停循环删除最不常用的数据直到不超过最大容量,最不常用的数据位于双链表的首位

public void trimToSize(int maxSize) {
     // 不停循环
     while (true) {
         K key;
         V value;
         synchronized (this) {
             if (size < 0 || (map.isEmpty() && size != 0)) {
                 throw new IllegalStateException(getClass().getName()
                         + ".sizeOf() is reporting inconsistent results!");
             }
             // 当前容量小于最大容量或无数据时才退出
             if (size <= maxSize || map.isEmpty()) {
                 break;
             }
             // 获取最少使用的元素
             Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
             key = toEvict.getKey();
             value = toEvict.getValue();
             // 删除元素
             map.remove(key);
             // 减去被删除的键值对大小
             size -= safeSizeOf(key, value);
             evictionCount++;
         }

         entryRemoved(true, key, value, null);
     }
 }

get

get方法同样首先会对key判空,然后通过key获取value,如果重写过create方法的话就会将该方法创建的value保存,但此时可能会有其他线程调用put方法先插入value,因此会做撤回处理

public final V get(@NonNull K key) {
    // key判空
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V mapValue;
    synchronized (this) {
        // 通过key查询value
        mapValue = map.get(key);
        if (mapValue != null) {
            hitCount++;
            // 不为空则返回value
            return mapValue;
        }
        missCount++;
    }
    
    // 为空则调用create方法,默认返回null,可重写
    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }

    synchronized (this) {
        createCount++;
        // 保存通过create方法创建的value
        mapValue = map.put(key, createdValue);
        // 判断是否有其他线程提前插入了value
        if (mapValue != null) {
            // There was a conflict so undo that last put
            // 有其他线程已插入value,将其他线程的value替换create方法的value
            map.put(key, mapValue);
        } else {
            // 无其他线程插入value,计算键值对的大小后累加
            size += safeSizeOf(key, createdValue);
        }
    }

    if (mapValue != null) {
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        trimToSize(maxSize);
        return createdValue;
    }
}

remove

remove方法比较简单,就是调用LinkedHashMap的remove方法,然后计算占用内存大小

public final V remove(@NonNull K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V previous;
    synchronized (this) {
        previous = map.remove(key);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }

    return previous;
}
发布了174 篇原创文章 · 获赞 119 · 访问量 55万+

猜你喜欢

转载自blog.csdn.net/lj402159806/article/details/104116446