說到Glide
就有點尷尬,我原本想出一篇《手撕Glide》,可是很遺憾,源碼實在太多了。寫着寫着就3000多字了,甚至還沒寫完,實在不合適,由於我寫文的原則是短小精悍,因此就暫時不出這篇文章了,此次就先講講Glide
都在用的LruCache
有什麼神奇之處。另外我抖音的面試在即,也不知道本身水平到了沒有,如今出一篇算一篇先。java
在項目中直接導入Glide
的庫,調用內部的LruCache
來看看效果。node
LruCache lruCache = new LruCache<String, Integer>(2);
lruCache.put("1", 1);
lruCache.put("2", 2);
lruCache.put("1", 1);
lruCache.put("3", 3);
System.out.println(lruCache.get("1"));
System.out.println(lruCache.get("2"));
System.out.println(lruCache.get("3"));
複製代碼
簡要說明代碼內容,建立一個空間爲2的存儲空間(這裏先不透漏內部結構),用put()
方法對數據進行存儲,再經過get()
對每一個數據進行一次獲取操做,而後咱們再來看看結果。面試
個人天!!2沒了? 這是怎麼一回事??爲了知道答案,那咱們只好進入Glide
的庫中看看緣由了。算法
先看看LruCache
的變量家庭裏有哪些小傢伙把。數組
public class LruCache<T, Y> {
// 容量爲100的雙向鏈表
private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
private final long initialMaxSize; // 初始化最大容量
private long maxSize; // 最大容量
private long currentSize; // 已存在容量
}
複製代碼
一樣對於LruCache
來講不也和HashMap
同樣只有三步驟要走嘛,那我就從這三個步驟入手探索一下LruCache
好了,可是咱們要帶上一個問題出發,initialMaxSize
的做用是什麼?緩存
public LruCache(long size) {
this.initialMaxSize = size;
this.maxSize = size;
}
複製代碼
到這裏想來讀者都已經知道套路了,也就初始化了初始化最大容量和最大容量,那就直接下一步。框架
public synchronized Y put(@NonNull T key, @Nullable Y item) {
// 返回值就是一個1
final int itemSize = getSize(item);
// 若是1大於等於最大值就無操做
// 也就說明整個初始化的時候並不能將size設置成1
if (itemSize >= maxSize) {
//用於重寫的保留方法
onItemEvicted(key, item);
return null;
}
// 對當前存在數據容量加一
if (item != null) {
currentSize += itemSize;
}
@Nullable final Y old = cache.put(key, item);
if (old != null) {
currentSize -= getSize(old);
if (!old.equals(item)) {
onItemEvicted(key, old);
}
}
evict(); // 1 -->
return old;
}
// 由註釋1直接調用的方法
private void evict() {
trimToSize(maxSize); // 2 -->
}
// 由註釋2直接調用的方法
protected synchronized void trimToSize(long size) {
Map.Entry<T, Y> last;
Iterator<Map.Entry<T, Y>> cacheIterator;
// 說明當前的容量大於了最大容量
// 須要對最後的數據進行一個清理
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
last = cacheIterator.next();
final Y toRemove = last.getValue();
currentSize -= getSize(toRemove);
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove);
}
}
複製代碼
這是一個帶鎖機制的方法,經過對當前容量和最大容量的判斷,來抉擇是否須要把咱們的數據進行一個刪除。可是問題依舊存在,initialMaxSize
的做用是什麼?,咱們可以知道的是maxSize
是一個用於控制容量大小的值。ide
public synchronized Y get(@NonNull T key) {
return cache.get(key);
}
複製代碼
那這就是調用了LinkedHashMap
中的數據,可是終究仍是沒有說出initialMaxSize
的做用。函數
initialMaxSize
這裏就不買關子了,由於其實就個人視角來看這個initialMaxSize
確實是沒啥用處的。哈哈哈哈哈!!!可是,又一個地方用到了它。post
public synchronized void setSizeMultiplier(float multiplier) {
if (multiplier < 0) {
throw new IllegalArgumentException("Multiplier must be >= 0");
}
maxSize = Math.round(initialMaxSize * multiplier);
evict();
}
複製代碼
也就是用於調控咱們的最大容量大小,可是我以爲仍是沒啥用,但是是我太菜了吧,這個方法沒有其餘調用它的方法,是一個咱們直接在使用過程當中使用的,可能和數據屢次使用的一個保存之類的問題相關聯把,場景的話也就相似Glide
的圖片緩存加載把。也但願知道的讀者能給我一個解答。
由於操做方式和HashMap
一致就再也不復述,就看看他的節點長相。
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
// 存在先後節點,也就是咱們所說的雙向鏈表
LinkedHashMapEntry<K,V> before, after;
LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
複製代碼
可是到這裏,我又出現了一個問題,爲何我沒有看到整個數據的移動?也就是最近使用的數據應該調換到最後開始的位置,他到底實在哪裏進行處理的呢?作一個猜測好了,既然是使用了put()
纔會形成雙向鏈表中數據的變換,那咱們就應該是須要進入對LinkedHashMap.put()
方法中進行查詢。
固然有興趣探索的讀者們,我須要提一個醒,就是此次的調用不能夠直接進行對
put()
進行查詢,那樣只會調用到一個接口函數,或者是抽象類函數,最適合的方法仍是使用咱們的斷點來進行探索查詢。
可是通過一段努力後,不斷深度調用探索發現這樣的問題,他最後會調用到這樣的問題。
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { } // 把數據移動到最後一位
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
複製代碼
這是以前咱們在瞭解HashMap
是並無發現幾個方法,上面也明確寫着爲LinkedHashMap
保留。哇哦!!那咱們的操做確定實在這些裏面了。
// --> HashMap源碼第656行附近調用到下方方法
// 在putVal()方法內部存在這個出現
afterNodeAccess(e);
// --> LinkedHashMap對其具體實現
// 就是將當前數據直接推到最後一個位置
// 也就是成爲了最近剛使用過的數據
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
複製代碼
好了,自此咱們也就清楚了整個鏈表的變換過程了。
LruCache
這是一個很是緊張刺激的環節了,擼代碼前,咱們來找找思路好了。
(1)存儲容器用什麼? 由於LinkedHashMap
的思路太過冗長,咱們用數組來從新完成整個代碼的構建
(2)關鍵調用方法put()
、get()
以及put()
涉及的已存在變量移位。
哇哦!看來要作的事情也並無這麼多,那咱們就先來看看第一次構造出來的框架好了。
public class LruCache {
private Object objects[];
private int maxSize;
private int currentSize;
public LruCache(int size){
objects = new Object[size];
maxSize = size;
}
/** * 插入item * @param item */
public void put(Object item){
}
/** * 獲取item * @param item */
public Object get(Object item){
return null;
}
/** * 根據下標對應,將後續數組移位 * @param index */
public void move(int index){
}
}
複製代碼
由於只要是數組變換就存在移位,因此移位操做是必不可少的。那咱們如今的工做也就是把數據填好了,對應的移位是怎麼樣的操做的思路了。
public class LruCache {
public Object objects[];
private int maxSize;
public int currentSize;
public LruCache(int size) {
objects = new Object[size];
maxSize = size;
}
/** * 插入item * * @param item */
public void put(Object item) {
// 容量未滿時分紅兩種狀況
// 1。 容器內存在
// 2。 容器內不存在
int index = search(item);
if (index == -1) {
if (currentSize < maxSize) { //容器未滿,直接插入
objects[currentSize] = item;
currentSize++;
} else { // 容器已滿,刪去頭部插入
move(0);
objects[currentSize - 1] = item;
}
}else {
move(index);
}
}
/** * 獲取item * * @param item */
public Object get(Object item) {
int index = search(item);
return index == -1 ? null : objects[index];
}
/** * 根據下標對應,將後續數組移位 * * @param index */
public void move(int index) {
Object temp = objects[index];
// 將後續數組移位
for (int i = index; i < currentSize - 1; i++) {
objects[i] = objects[i + 1];
}
objects[currentSize - 1] = temp;
}
/** * 搜尋數組中的數組 * 存在則返回下標 * 不存在則返回 -1 * @param item * @return */
private int search(Object item) {
for (int i = 0; i < currentSize; i++) {
if (item.equals(objects[i])) return i;
}
return -1;
}
複製代碼
由於已經真的寫的比較詳細了,也沒什麼難度的擼了個人20分鐘,但願讀者們可以快入入門,下面給出個人一份測試樣例,結束這個話題。
想來咱們都知道在操做系統中有這樣的問題須要思考,具體題型的話就是缺頁中斷。 用一個例題來完全瞭解LruCache
的算法。
例: 存入內存的數據序列爲:(1,2,1,3,2),內存容量爲2。
最近使用 | 最久未使用 | 動做 |
---|---|---|
1 | 1入內存 | |
2 | 1 | 2入內存 |
1 | 2 | 1入內存,交換1和2的使用頻率 |
3 | 1 | 3入內存,內存不足,排出2 |
2 | 3 | 2入內存,內存不足,排出1 |
LruCache 主要用於緩存的處理,這裏的緩存主要指的是內存緩存和磁盤緩存。
以上就是個人學習成果,若是有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。
相關文章推薦: