• Java集合之LinkedHashMap


    LinkedHashMap是HashMap的子类,通过维护一个双向链表,实现Map有序遍历元素的特性。

    因此,对于LinkedHashMap来说,其基本特性如下:

    基本特性 结论
    元素是否允许为null key和value可以为null
    元素是否允许重复 key重复会覆盖,value可以重复
    是否有序
    是否线程安全

    源码分析

    本文使用的是JDK 1.8.0_201的源码。

    成员变量

    LinkedHashMap是继承HashMap的成员变量,实现有序的特性,还维护了额外几个成员变量:

    成员变量 作用
    transient LinkedHashMap.Entry<K,V> head; 链表的头部
    transient LinkedHashMap.Entry<K,V> tail; 链表的尾部
    final boolean accessOrder; 遍历的模式,默认是false

    put操作

    LinkedHashMap没有实现自己的put操作,继承自HashMap。问题在于,LinkedHashMap是怎么维护元素的插入顺序的呢?换句话说,LinkedHashMap的双向链表是在哪里维护的?答案是在HashMap put方法中调用的模板方法newNode()。HashMap中存在许多的模板方法,以方便LinkedHashMap去实现自己的特性。

    对于LinkedHashMap来说,newNode()方法实现如下:

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        // 创建一个Entry
        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;
        }
    }
    

    get操作

    LinkedHashMap自己实现了get()方法,代码如下:

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        // 与HashMap的区别在这里
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }
    

    上面的代码与HashMap的区别在于accessOrder条件的判断,如果accessOrder为true,程序就会去执行afterNodeAccess(e)方法。

    LRU缓存实现

    accessOrder变量,是LinkedHashMap中一个有趣的属性。我们先看源码文档注释:

    A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification.

    对于insertion-ordered模式来说,即accessOrder为false的情况,只有添加和删除操作,才会改变元素的顺序。而对于access-ordered模式来说,即accessOrder为true的情况,即使是get()操作,都会改变元素的顺序。

    access-ordered模式,是实现LRU缓存(最近最少使用)的关键。所谓最近最少使用,就是说当缓存满了,优先淘汰那些最近最少被访问的数据。

    LinkedHashMap的具体实现就是,每次访问时,都把访问的数据移到双向队列的尾部,那么队列头部的元素就是最少被访问的数据,每次要淘汰数据时,就删除队列头部的数据。

    回过头再看前面get操作中的afterNodeAccess(e)方法,就会发现这个方法正是每次访问时,把访问数据移到队尾的关键。

    总结

    1. LinkedHashMap是有序的吗?它是如何保证有序的?

    LinkedHashMap通过维护一个双向链表,从而保证元素的顺序。需要注意的是,LinkedHashMap保证的有序是指元素的插入顺序或者访问顺序,而不是指元素字典顺序。至于LinkedHashMap具体保证的是插入顺序还是访问顺序,通过初始化时accessOrder字段的值来确定。如果accessOrder=true,表示访问顺序,false表示插入顺序。

    2. 如何使用LinkedHashMap实现一个LRU缓存?

    LinkedHashMap有两种维护顺序的模式,insetion-ordered模式跟access-ordered模式,当成员变量accessOrder为true,即access-ordered模式时,每次访问元素时,就会将元素移到队列的尾部,这样头部的元素就成了最少被访问的元素。当需要淘汰数据时,头部的元素先被淘汰。这个特性非常适合用来实现LRU缓存。

    至于何时淘汰数据,LinkedHashMap提供了一个模板方法removeEldestEntry()。具体做法如下:

    public class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private final int size;
    
        public LRUCache(int size) {
            super(size, 0.75f, true);
            this.size = size;
        }
    
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            // 当缓存的元素个数大于设置的大小时,淘汰最老的那个元素
            return this.size() > this.size;
        }
    }
    
    3. 为什么LinkedHashMap的遍历性能会比HashMap好?

    因为LinkedHashMap的遍历是通过双向链表实现的,与size的大小有关,即与map的实际元素个数有关。与此同时,HashMap的遍历,是需要遍历底层数组的,举个例子,一个只存放了一个元素的HashMap,其底层数组的大小至少是16,那么这个遍历有15次是多余的。由于负载因子的存在(不考虑负载因子大于0的情况),HashMap的遍历总是会存在多余的次数。

  • 相关阅读:
    如何阅读大型项目的代码?
    常用法律常识链接
    ubuntu 常用参数设置
    访问内存过程小结
    Netfilter深度解剖
    linux中的namespace
    style="display:none"隐藏html的标签
    xshell用ssh连接VMware中的ubuntu
    rails rake和示例
    rails中render 和 redirect_to的区别, each只能用在数组中,如果只有一个或者零个项,用each方法会报错undefined method `each' for #...
  • 原文地址:https://www.cnblogs.com/bluemilk/p/10733215.html
Copyright © 2020-2023  润新知