zoukankan      html  css  js  c++  java
  • 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的遍历总是会存在多余的次数。

  • 相关阅读:
    ABAP接口用法
    监听textarea数值变化
    The first step in solving any problem is recognizing there is one.
    Wrinkles should merely indicate where smiles have been.
    God made relatives.Thank God we can choose our friends.
    Home is where your heart is
    ABAP跳转屏幕
    Python 工具包 werkzeug 初探
    atom通过remote ftp同步本地文件到远程主机的方法
    Mongodb学习笔记一
  • 原文地址:https://www.cnblogs.com/bluemilk/p/10733215.html
Copyright © 2011-2022 走看看