LinkedHashMap源码解读

2018-01-01  本文已影响0人  Noblel

先看几个构造方法

/**
 * 双向链表的头部
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * 双向链表的尾端
 */
transient LinkedHashMap.Entry<K,V> tail;

public LinkedHashMap() {
    super();
    //accessOrder为false则表示按插入顺序排序
    //accessOrder为true则表示按访问顺序排序
    accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor, 
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

/**
 * 重写父类的newNode方法
 */ 
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

/**
 * 把新加的节点放在链表的最后面
 */
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

/**
 * 继承于HashMap的Entry,添加了上一个和下一个节点由单向链表变成了双向链表
 */
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

/**
 * 这个方法是在调用put的时候,hashMap中的putVal()方法中的afterNodeAccess重写的
 * 在put的时候已经有值了会更新值然后调用此方法
 * 把当前节点移动到双向链表的最末端
 */
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    //accessOrder默认是false的
    //e不在最末尾就修改链表,将e移到链表最末尾的操作
    if (accessOrder && (last = tail) != e) {
        //b是e的前一个节点,a是e的后一个节点
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<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;
        //修改计数器自加1
        ++modCount;
    }
}

/**
 * 插入完成会掉用此方法,传过来的是false
 */ 
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

/**
 * 这个方法一直返回的是false,那就是不需要删除末尾节点,
 * 自己写的时候可以重写给定一个条件控制LinkedHashMap中最老数据在何时删除
 */
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

/**
 * 虽然没有重写put方法,但是重写了get方法
 */
public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    //如果启动了访问模式就调用
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

这个有啥用啊?写了那么多。。作为小白我以前自己开发的时候从来没想过那么多,看到大家都在讨论图片缓存,所以看了一下网上的代码。做出了一些思考,我们显示图片是可以先从内存中获取,然后内存中没有就去本地缓存文件找,本地也没有就去网络获取。这样可以减少服务器的压力,不要每次都访问服务器。

使用方法

private LruCache<String, Bitmap> mMemoryCache;  

public ImageDownLoader(Context context){  
    //获取系统分配给每个应用程序的最大内存,不同的厂商手机给的最大值不一样
    int maxMemory = (int) Runtime.getRuntime().maxMemory();    
    int mCacheSize = maxMemory / 8;  
    //给LruCache分配1/8
    mMemoryCache = new LruCache<String, Bitmap>(mCacheSize){  

        /**
         * 必须重写此方法,来测量Bitmap的大小  
         */
        @Override  
        protected int sizeOf(String key, Bitmap value) {  
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                return value.getAllocationByteCount();
            }
            return value.getHeight() * value.getRowBytes();
        }  
        
        /**
         * 当缓存被移除时调用,可不重写
         * @param evicted 缓存移除的原因,true表示内存不够移除了,false表示put移除或者remove移除了
         * @param key key
         * @param oldValue
         * @param newValue
         */
        @Override
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
            super.entryRemoved(evicted, key, oldValue, newValue);
        }

        /**
         * 获取不到的时候调用,可不重写
         * @param key
         * @return
         */
        @Override
        protected Bitmap create(String key) {
            return super.create(key);
        }  
    };  
}

//添加到缓存
mMemoryCache.put(key, bitmap);

//获取缓存
mMemoryCache.get(key);

看一下LruCache

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    //直接new了一个LinkedHashMap,把accessOrder设为true,也就是按访问顺序排序
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

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);
        }
    }

    //有旧值就执行entryRemoved方法,这是一个空实现,可以自己重写
    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }

    trimToSize(maxSize);
    return previous;
}

/**
 * 看到这我们就知道为什么需要重写了
 */
protected int sizeOf(K key, V value) {
    return 1;
}

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<K, V> toEvict = map.eldest();
            if (toEvict == null) {
                break;
            }

            key = toEvict.getKey();
            value = toEvict.getValue();
            //移除最不常用的节点
            map.remove(key);
            size -= safeSizeOf(key, value);
            //回收次数自加
            evictionCount++;
        }
        //调用entryRemoved方法,这里true就是说被迫移除
        entryRemoved(true, key, value, null);
    }
}

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

    V mapValue;
    synchronized (this) {
        //从LinkedHashMap中获取,获取方式还是hashMap的查找方式,只是内部维护一个链表做一些删除元素的操作。
        mapValue = map.get(key);
        if (mapValue != null) {
            //命中数自加
            hitCount++;
            return mapValue;
        }
        //miss数自加
        missCount++;
    }
    
    //这个create方法可重写,也就是LinkedHashMap中没有的时候会调用到这里
    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表示内存
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        trimToSize(maxSize);
        return createdValue;
    }
}

最后自己画一个图总结了一下,有错误请指正。共同进步学习。

原理图
上一篇下一篇

猜你喜欢

热点阅读