LruCache源码分析

LruCache类里面有一个LinkedHashMap map的变量,缓存主要就是用这个map来做的,lru算法也是linkedMap来实现的。

对这个类的源码分析主要是以下几个目的:

  • get方法
  • put方法
  • 容量控制
  • lru算法的实现
public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        //第一段代码
        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value. 
         */
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

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

我们看这段代码,首先就根据key从map拿缓存的数据,如果有数据,直接返回,命中数加加。
如果没有命中,则根据key 创建一个缓存对象,LruCache中create方法直接返回null,这里如果我们要实现LruCache就需要自己实现这个方法,例如我们可以在create中做这样的工作---从磁盘读取对象。创建成功的话逻辑继续往下走。map会把创建的对象put进去,这里注意linkedHashMap的put方法返回的值有俩种情况:

  • null,如果map中没有key对应的缓存对象。
  • previous value,如果map中已经有了一个key对应的缓存对象。
    map的put操作返回的如果是一个缓存对象,则说明该key对应的已经有了一个缓存对象,所以就把previous value重新put进去

这里需要注意俩点,第一点是把以前的value重新put进去,而不是替换了
第二点是前面map.get(key)为空的时候才会执行到这一步,为什么这里map.put(key)的时候会存在以前的对
象呢?这里就要考虑实际的使用环境下很大可能是多线程的情况,这个时候有可能另外一个线程已经先行一步已经把另外一个缓存对象put进去了。
entryRemoved 这个方法是空的实现。
如果是根据key新创建的缓存put进map了,那么就要控制容量,这就要使用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) {
                    break;
                }

                Map.Entry toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

这个方法比较简单,就是一个死循环,判断size,如果size未到达阈值,则break跳出循环。如果到达阈值,则使用LinkedMap取出最后面(最近最少使用)的缓存,删除,更新size。

现在再来看看put方法

public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

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

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

        trimToSize(maxSize);
        return previous;
    }

这个方法比较简单,map.put,更新size,并判断返回的值,如果返回的值不为空(也就是说明该key对应有缓存,也就是key值冲突了),和get方法里不一样的是,这里就直接替换掉了。然后再调用trimToSize方法进行容量控制。

最后就是Lru算法的实现了,我们到LinkedHashMap源码里一看,LinkedHashMap里在get方法调用后,如果accessOrder是true的话就会调用afterNodeAccess(Node)这个方法,


void afterNodeAccess(Node e) { // move node to last
        LinkedHashMapEntry last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMapEntry p =
                (LinkedHashMapEntry)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;
        }
    }

就是把刚才get的缓存放到链表尾部,edest方法返回的是链表头部(head),这样的逻辑是合理的,最近使用的就放链表尾部,那链表头部就是最近最少使用的缓存。

你可能感兴趣的:(LruCache源码分析)