Android开发Android开发Android技术知识

懂LruCache?你必须先懂LinkedHashMap,顺带给

2019-11-22  本文已影响0人  酱爆大头菜

上一篇 LruCache缓存机制,深入浅出,发现了一个源码bug 中我们介绍了LruCache的使用和原理,同时也提到了LruCache本质就是在维护一个LinkedHashMap,具体为什么是LinkedHashMap而不是其他对象,我们通过以下几个问题来解释原因。

1.LinkedHashMap是个什么东西?

2. LinkedHashMap是干什么用的?

3. LinkedHashMap怎么用?

例 1

    public class LinkedHashMapActivity extends AppCompatActivity {
        LinkedHashMap<Integer, Integer> linkedHashMap;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_linked_hash_map);
            linkedHashMap = new LinkedHashMap<Integer, Integer>(2);
            linkedHashMap.put(1, 1);
            linkedHashMap.put(2, 2);
            linkedHashMap.put(3, 3);
            for (Map.Entry<Integer, Integer> a : linkedHashMap.entrySet()) {
                Log.e("TAG", "key->" + a.getKey() + "");
                Log.e("TAG", "value->" + a.getValue() + "");
            }
        }
    }

        2019-11-21 14:32:17.332 3310-3310/com.we.we E/TAG: key->1
        2019-11-21 14:32:17.333 3310-3310/com.we.we E/TAG: value->1
        2019-11-21 14:32:17.333 3310-3310/com.we.we E/TAG: key->2
        2019-11-21 14:32:17.333 3310-3310/com.we.we E/TAG: value->2
        2019-11-21 14:32:17.333 3310-3310/com.we.we E/TAG: key->3
        2019-11-21 14:32:17.333 3310-3310/com.we.we E/TAG: value->3

例 2

    public class LinkedHashMapActivity extends AppCompatActivity {
        LinkedHashMap<Integer, Integer> linkedHashMap;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_linked_hash_map);
            linkedHashMap = new LinkedHashMap<Integer, Integer>(2) {
                @Override
                protected boolean removeEldestEntry(Entry eldest) {
                    return linkedHashMap.size() > 2;
                }
            };
            linkedHashMap.put(1, 1);
            linkedHashMap.put(2, 2);
            linkedHashMap.put(3, 3);
            for (Map.Entry<Integer, Integer> a : linkedHashMap.entrySet()) {
                Log.e("TAG", "key->" + a.getKey() + "");
                Log.e("TAG", "value->" + a.getValue() + "");
            }
        }
    }

        2019-11-21 14:34:17.708 3421-3421/com.we.we E/TAG: key->2
        2019-11-21 14:34:17.708 3421-3421/com.we.we E/TAG: value->2
        2019-11-21 14:34:17.708 3421-3421/com.we.we E/TAG: key->3
        2019-11-21 14:34:17.708 3421-3421/com.we.we E/TAG: value->3

例 3

    public class LinkedHashMapActivity extends AppCompatActivity {
        LinkedHashMap<Integer, Integer> linkedHashMap;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_linked_hash_map);
            linkedHashMap = new LinkedHashMap<Integer, Integer>(2) {
                @Override
                protected boolean removeEldestEntry(Entry eldest) {
                    return linkedHashMap.size() > 2;
                }
            };
            linkedHashMap.put(1, 1);
            linkedHashMap.put(2, 2);
            //调用get进行排序
            linkedHashMap.get(1);
            for (Map.Entry<Integer, Integer> a : linkedHashMap.entrySet()) {
                Log.e("TAG", "key->" + a.getKey() + "");
                Log.e("TAG", "value->" + a.getValue() + "");
            }
        }
    }
        2019-11-21 14:55:08.481 3843-3843/com.we.we E/TAG: key->1
        2019-11-21 14:55:08.481 3843-3843/com.we.we E/TAG: value->1
        2019-11-21 14:55:08.481 3843-3843/com.we.we E/TAG: key->2
        2019-11-21 14:55:08.481 3843-3843/com.we.we E/TAG: value->2

例 4

    public class LinkedHashMapActivity extends AppCompatActivity {
        LinkedHashMap<Integer, Integer> linkedHashMap;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_linked_hash_map);
            //主要看最后这个参数,默认是false。
            linkedHashMap = new LinkedHashMap<Integer, Integer>(2,0.75f,true) {
                @Override
                protected boolean removeEldestEntry(Entry eldest) {
                    return linkedHashMap.size() > 2;
                }
            };
            linkedHashMap.put(1, 1);
            linkedHashMap.put(2, 2);
            //调用get进行排序
            linkedHashMap.get(1);
            for (Map.Entry<Integer, Integer> a : linkedHashMap.entrySet()) {
                Log.e("TAG", "key->" + a.getKey() + "");
                Log.e("TAG", "value->" + a.getValue() + "");
            }
        }
    }
        2019-11-21 15:07:46.672 4071-4071/com.we.we E/TAG: key->2
        2019-11-21 15:07:46.672 4071-4071/com.we.we E/TAG: value->2
        2019-11-21 15:07:46.672 4071-4071/com.we.we E/TAG: key->1
        2019-11-21 15:07:46.672 4071-4071/com.we.we E/TAG: value->1

4. LinkedHashMap的原理是什么?怎么实现的?

4.1. 首先我们了解LinkedHashMap的继承关系图,实线代表继承关系,虚线代表实现接口。

LinkedHashMap继承关系图.jpg

4.2. LinkedHashMap的双向链表对象都包含什么属性?

//LinkedHashMap.LinkedHashMapEntry
//继承于 HashMap.Node,获得普通链表的能力,同时通过内部属性 before, after;维护双向链表。
 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);
        }
    }

// HashMap.Node
// 链表元素对象
 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        //元素的key
        final K key;
        //值value
        V value;
        //下一个元素的数据
        Node<K,V> next;

       ...省略若干代码
    }

// HashMap.TreeNode
//红黑树对象,主要看继承关系,如需了解内部逻辑可通过HashMap原理进行了解。
static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
  }
LinkedHashMap.LinkedHashMapEntry继承关系图.jpg
补充一点小知识

4.3. LinkedHashMap对象是怎么维护每一个双向链表对象的?

    //HashMap的put()方法,同时也是LinkedHashMap的put方法
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
    //直接看关键部分的代码,看我注释的部分
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            //调用了newNode()方法,这里用到了一个面向对象里多态的特性,而LinkedHashMap重写了newNode()方法
            //如果是LindedHashMap对象调用,触发的是LinkedHashMap重写的newNode()
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //有重复的key,则用待插入值进行覆盖,返回旧值。
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //HashMap中该方法没做任何操作,LinkedHashMap进行了重写实现,并调用。
                //该方法主要将当前节点移动到双向链表尾部,后续进行详细分析
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        //每次put新元素都会调用(如果是更新已有元素则不调用,因为没有新增元素,不会导致size+1)
        //但是HashMap中该方法没做任何操作, LinkedHashMap进行了重写实现.
       //主要用于移除最早的元素,后续详细解析
        afterNodeInsertion(evict);
        return null;
    }

    //HashMap内的newNode
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

    //LinkedHashMap重写的newNode
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
         //构造带双向链表属性的对象。
        LinkedHashMapEntry<K,V> p =
            new LinkedHashMapEntry<K,V>(hash, key, value, e);
        //双向链表维护
        linkNodeLast(p);
        return p;
    }

  private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
         //首先获取当前链表的最后一个元素
        LinkedHashMapEntry<K,V> last = tail;
        //当前插入的元素定义为最后一个元素
        tail = p;
        if (last == null)
             //如果之前的最后元素是null,说明之前的链表就是空的,所以当前的元素是一个元素。
            head = p;
        else {
            //如果之前的链表不是null
            //put前的最后一个元素设置为当前put元素的前一个。
            p.before = last;
            //当前put元素设置为put前最后一个元素的下一个
            last.after = p;
        }
    }
//这个方法是在HashMap的代码里调用的,在put方法中调用的时候参数evict传的是true
 void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMapEntry<K,V> first;
        //evict是true,(first = head)=true,而removeEldestEntry()方法默认返回的是false,因此if内的逻辑默认是不执行的。
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            //移除链表头部的元素
            removeNode(hash(key), key, null, false, true);
        }
    }

    //HashMap中的removeNode方法,主要用于删除某一个元素
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
         ...省略若干代码
    }
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMapEntry<K,V> last;
         //accessOrder 是LinkedHashMap实例化的时候的入参,需手动传true该方法的逻辑才会启用。
        if (accessOrder && (last = tail) != e) {
            LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
            //p为当前尾节点,因此p.after = null;
            p.after = null;
            //p.before==null,说明p以前是头节点,但是现在需要把p放在尾节点,则以前p.after就变成了头节点。
            if (b == null)
                head = a;
            else
                //原来的顺序是b <-> p <-> a...现在p需要移动到尾部,则就变成了b <-> a...... <->p
                b.after = a;
            if (a != null)
                //原来a.before是p,现在p移动走了,那p.before就变成了a.before
                a.before = b;
            else
                //如果之前p就是尾节点,则将last引用p.before
                last = b;
            if (last == null)
                //如果原来尾节点是空,则说明当前链表只有一个节点,因此head引用p
                head = p;
            else {
                //之前尾节点不为空,因为p移动到了最后,因此p.before引用原尾节点,原尾节点.after引用p
                p.before = last;
                last.after = p;
            }
            tail = p;
            //计数器自增1
            ++modCount;
        }
    }

4.4. LinkedHashMap为什么调用get()就会触发排序?

    //LinkedHashMap重写的get方法
    public V get(Object key) {
        Node<K,V> e;
        //getNode()调用是HashMap.getNode()
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            //哈哈哈,看到这我一下子就明白他怎么做的排序了。
            afterNodeAccess(e);
        return e.value;
    }

4.4. LinkedHashMap调用remove()后链表怎么维护?

//HashMap.remove()
  public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

    //HashMap.removeNode(),核心的删除方法
    final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                //维护双向链表的after...()方法,在HashMap中该方法是空方法。
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

    //LinkedHashMap重写的afterNodeRemoval()方法
    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        //首先删除元素的前后节点引用置空
        p.before = p.after = null;
        if (b == null)
            //如果当前元素的前节点是null,则证明当前元素原来是头节点。
            //因此删除当前元素后,当前元素的后节点就变成了头节点。
            head = a;
        else
            //如果前节点不为null,则前节点的after引用后节点。
            b.after = a;
        if (a == null)
            //如果当前元素的后节点为null,则说明当前元素为尾节点,删除当前元素后,尾节点变成当前元素的前节点。
            tail = b;
        else
            //尾节点不为null,尾节点的前节点引用删除元素的前节点。
            a.before = b;
    }

至此,LinkedHashMap的继承关系,添加,获取,移除三大主要方法,以及内部的排序处理逻辑已经分析完了,HashMap凭借强大的设计模式,让LinkedHashMap重写了以下3个必要方法就基本实现了全部功能。


接下来我们针对一些LinkedHashMap的特性补充一点小知识。

    //LinkedHashMap.containsValue()
    public boolean containsValue(Object value) {
        //循环查找所有键值对的中和value重复的数据
        for (LinkedHashMapEntry<K,V> e = head; e != null; e = e.after) {
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }

    //HashMap.containsValue()
    public boolean containsValue(Object value) {
        Node<K,V>[] tab; V v;
        if ((tab = table) != null && size > 0) {
            //双重for循环查找数据
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }
// LinkedHashMap.LinkedHashIterator.nextNode()
final LinkedHashMapEntry<K,V> nextNode() {
            LinkedHashMapEntry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }

//HashMap.HashIterator.nextNode()
 final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                 //循环HashMap中给每一个桶
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

总结和一点小建议
上一篇 下一篇

猜你喜欢

热点阅读