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にはコンストラクション関数が1つしかありません.入力されたパラメータは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;
}