美文网首页
LinkedHashMap原理分析

LinkedHashMap原理分析

作者: 后来丶_a24d | 来源:发表于2020-04-14 15:25 被阅读0次

    目录

    • put操作
    • remove操作
    • lru缓存实现

    put操作

    • LinkedHashMap继承HashMap, 大部分直接用HashMap的方法。TreeMap底层红黑树,利用红黑树的性质,实现了键值对排序功能。
    • hashmap put操作时未如果在单链表中找到要插入的节点,将新节点接在单链表的后面,调用的是newNode方法
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }
    
    • LinkedHashMap 中覆写了newNode方法以维护插入有序, 其中Entry与HashMap的Node相比多了前驱后驱节点。整个LinkedHashMap 维护head -> 各个节点 -> tail整个双端链表
    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);
        // 将 Entry 接在双向链表的尾部
        linkNodeLast(p);
        return p;
    }
    
    // LinkedHashMap 中实现
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        // last 为 null,表明链表还未建立
        if (last == null)
            head = p;
        else {
            // 将新节点 p 接在链表尾部
            p.before = last;
            last.after = p;
        }
    }
    

    remove操作

    • hashmap的删除元素操作最后会调用一个回调函数afterNodeRemoval,LinkedHashMap会复写此函数
    // LinkedHashMap 中覆写
    void afterNodeRemoval(Node<K,V> e) { 
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // 将 p 节点的前驱后后继引用置空
        p.before = p.after = null;
        // b 为 null,表明 p 是头节点
        if (b == null)
            head = a;
        else
            b.after = a;
        // a 为 null,表明 p 是尾节点
        if (a == null)
            tail = b;
        else
            a.before = b;
    }
    

    lru缓存实现

    访问顺序的维护
    • 在初始化 LinkedHashMap,指定 accessOrder 参数为 true,即可让它按访问顺序维护链表。在get方法的时候如果判断需要维护访问顺序则会把最近访问元素放到末尾。
    // LinkedHashMap 中覆写
    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        // 如果 accessOrder 为 true,则调用 afterNodeAccess 将被访问节点移动到链表最后
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }
    
    // LinkedHashMap 中覆写
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            // 如果 b 为 null,表明 p 为头节点
            if (b == null)
                head = a;
            else
                b.after = a;
                
            if (a != null)
                a.before = b;
            /*
             * 这里存疑,父条件分支已经确保节点 e 不会是尾节点,
             * 那么 e.after 必然不会为 null,不知道 else 分支有什么作用
             */
            else
                last = b;
        
            if (last == null)
                head = p;
            else {
                // 将 p 接在链表的最后
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }
    
    lru实现
    • HashMap在put时候会调用afterNodeInsertion,LinkedHashMap重写了
    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);
        }
    }
    
    // 移除最近最少被访问条件之一,通过覆盖此方法可实现不同策略的缓存
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
    
    例子加测试
    public class Test {
    
    
        public static void main(String[] args) {
            SimpleCache<Integer, Integer> cache = new SimpleCache<Integer, Integer>(3);
    
            for (int i = 0; i < 10; i++) {
                cache.save(i, i * i);
            }
    
            System.out.println("插入10个键值对后,缓存内容:");
            System.out.println(cache + "\n");
    
            System.out.println("访问键值为7的节点后,缓存内容:");
            cache.getOne(7);
            System.out.println(cache + "\n");
    
            System.out.println("插入键值为1的键值对后,缓存内容:");
            cache.save(1, 1);
            System.out.println(cache);
        }
    }
    
    
    class SimpleCache<K, V> extends LinkedHashMap<K, V> {
    
        private int limit;
    
        public SimpleCache(int limit) {
            super(limit, 0.75f, true);
            this.limit = limit;
        }
    
        public V save(K key, V val) {
            return put(key, val);
        }
    
        public V getOne(K key) {
            return get(key);
        }
    
        public boolean exists(K key) {
            return containsKey(key);
        }
    
        /**
         * 判断节点数是否超限
         *
         * @param eldest
         * @return 超限返回 true,否则返回 false
         */
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > limit;
        }
    }
    

    参考文章

    相关文章

      网友评论

          本文标题:LinkedHashMap原理分析

          本文链接:https://www.haomeiwen.com/subject/ydctvhtx.html