浅谈HashMap,LinkedHashMap,ConcurrentHashMap、synchronizedMap(二.LinkedHashMap)

说完HashMap,我们我们如果想要有序的Map怎么办。取出来之后再排序,不是太复杂。
所以我们有LinkedHashMap。

LinkedHashMap

既然LinkedHashMap是有序的,那么为什么是有序的?首先我们从名字上分析,LinkedHashMap和HashMap不是继承就是实现,那么数据结构应该也是类似的,采用hash表(数组+链表的形式)。等下,HashMap中既然存在链表问什么不是有序的?让我们带着问题看源码吧。

类关系

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

从源码可知,LinkedHashMap继承HashMap。

属性

    /**
     * HashMap中的Node节点,在LinkedHashMap中变成了entry。
     * 同时增加了before和after属性。
     */
    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);
        }
    }

    private static final long serialVersionUID = 3801124242820219131L;

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

    /**
     * 双链表尾节点
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * 用来判断丽链表排序顺序:
     * true:插入顺序排序
     * fasle:LRU算法(Least recently used,最近最少使用)。
     */
    final boolean accessOrder;

我们看下不仅Entry属性有befor和after字段,而且有双链表头结点属性,尾节点属性。很容易就想到双链表啊!!所以LinkedHashMap数据结构,不仅仅是数组加链表。而且还有双链表。

数据结构

数据结构
总结一下,LinkedHashMap在Hash表的结构上增加了双向链表,类似于上图。但其实有没有想过,Hash表中其实已经存在链表的结构,为什么还要增加双向链表。在印象中,链表就是有序的。那Hash表中的链表和LinkedHashMap中的链表有什么不同呢?我认为有以下几点:

  1. 桶中的链表是散列表结构的一部分;而双向链表是LinkedHashMap的额外引入;
  2. 桶中的链表只做数据存储,因为生了hash碰撞,导致数据散落在一个桶中,用链表给予存储。没有存储顺序的概念;而LinkedHashMap的双向链表,是将插入Map的数据,按插入顺序(默认)存储起来。所以有序。

构造函数

    /**
     * 按照输入的大小以及加载因子创建对象。
     * accessOrder = false;,默认不开启LRU规则,按照插入循序排序。
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    /**
     * 按照给定的大小创建对象。加载因子为默认大小:0.75f。
     * accessOrder = false;,默认不开启LRU规则,按照插入循序排序。
     */
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    /**
     * 按照默认的大小以及默认的加载因子大小创建对象。
     * accessOrder = false;,默认不开启LRU规则,按照插入循序排序。
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    /**
     * 新建一个哈希表,同时将另一个map m 里的所有元素加入表中。
     * 默认加载因子以及足够大的容量。如果m为空,报NullPointerException。
     * accessOrder = false;,默认不开启LRU规则,按照插入循序排序。
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

    /**
     * 按照给定的大小,加载因子创建对象。
     * 如果accessOrder = false;,不开启LRU规则,按照插入循序排序。
     * 否则开启LRU规则,最近访问放在双向链表最后面。
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

既然是集成了HashMap那么一定会继承或者重写一些方法。
我们没有在源码中看到put以及remove方法。由此可知,put以及remove是继承了HashMap的方法,并没有重写,直接调用。那既然要维护双向链表,所以在HahsMap源码中,有三个方法没有实现。但是在LinkedHashMap中实现了

重写方法

afterNodeAccess

    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;
            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;
            ++modCount;
        }
    }

这段代码的意思简洁明了,就是把当前节点e移至链表的尾部。因为使用的是双向链表,所以在尾部插入可以以O(1)的时间复杂度来完成。并且只有当accessOrder设置为true时,才会执行这个操作。在HashMap的putVal方法中,就调用了这个方法。

afterNodeInsertion

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

afterNodeInsertion方法是在哈希表中插入了一个新节点时调用的,它会把链表的头节点删除掉,删除的方式是通过调用HashMap的removeNode方法。想一想,通过afterNodeInsertion方法和afterNodeAccess方法,是不是就可以简单的实现一个基于最近最少使用(LRU)的淘汰策略了?当然,我们还要重写removeEldestEntry方法,因为它默认返回的是false。

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

afterNodeInsertion()由于removeEldestEntry()所返回的false无执行意义。也就意味着如果想要让它有意义必须重写removeEldestEntry()。

afterNodeRemoval

    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

这个方法是当HashMap删除一个键值对时调用的,它会把在HashMap中删除的那个键值对一并从链表中删除,保证了哈希表和链表的一致性。

get

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);我IE䦹·
        return e.value;
    }

LinkedHashMap同样重写了get,如果afterNodeAccess为true,调用afterNodeAccess方法,维护LRU规则。

总结

因为HashMap不具备顺序性,所以产生了LinkedHashMap。继承HashMap,增加属性。同时其内部比HashMap增加了双向链表。并重写了部分HashMap方法。来确保其内部有序。

### 回答1: HashMapHashMap是一种基于哈希表的Map接口的非同步实现,它允许null键和null值。它不保证映射的顺序,特别是它不保证该顺序恒久不变。应用场景:HashMap通常用于查找表中的元素,因为它以常数时间复杂度进行查找操作。LinkedHashMapLinkedHashMap是一种基于哈希表和链表的Map接口的非同步实现,允许null键和null值。它保留插入映射的顺序,或者根据构造函数中指定的顺序,对其进行访问或遍历。应用场景:LinkedHashMap主要用于实现缓存,以便将最近使用的元素存储在映射中,并可以在将来的访问中快速访问。IdentityHashMap:IdentityHashMap是一种基于哈希表的Map接口的非同步实现,它根据对象的==运算符而不是equals()方法对键进行比较,允许使用null键和null值。应用场景:IdentityHashMap用于跟踪对象的身份,而不是它们的值。ConcurrentHashMapConcurrentHashMap是一种基于分段锁定和哈希表的Map接口的非同步实现,它允许多个修改并发进行,而不会引起数据不一致的情况。应用场景:ConcurrentHashMap可以在多线程环境中高效地读取和修改映射,因此它在并发编程中非常有用。 ### 回答2: HashMapHashMapJava中最常用的数据结构之一,它是基于哈希表实现的,具有快速的查找和插入操作的能力。HashMap中存储的键值对是无序的,不保证插入顺序和访问顺序一致。适合于大部分普通的键值对存储场景。 LinkedHashMapLinkedHashMapHashMap的子类,除了具有HashMap的特点外,还额外维护了一个双向链表,用于记录插入顺序。因此,LinkedHashMap可以保证插入顺序和访问顺序一致。适合于需要按照插入顺序进行迭代的场景。 IdentityHashMap:IdentityHashMap实现了基于引用相等性的哈希表,即使用"=="判断键的相等性而非equals方法。IdentityHashMap适合于需要精确控制键的相等性的场景,比如在并发环境下需要使用多个线程同时修改键,而保持键的唯一性。 ConcurrentHashMapConcurrentHashMapHashMap的线程安全版本,是Java集合框架中的一员。它通过分段锁(Segment)的方式实现了高度的并发性能,允许多个线程同时进行读操作,而不会阻塞。ConcurrentHashMap适合于高并发场景中需要进行频繁的读写操作的情况,比如并发缓存。 综上所述,HashMap适用于大部分键值对存储场景;LinkedHashMap适合于需要保持插入顺序的场景;IdentityHashMap适合于需要精确控制键的相等性的场景;ConcurrentHashMap适用于高并发的读写操作场景。 ### 回答3: HashMapHashMap是一种无序的数据结构,它使用键值对的方式存储数据。它的特点是效率高,可以快速的插入、删除和查找元素。HashMap适合用于不需要保持元素顺序的场景,例如缓存、索引等。 LinkedHashMapLinkedHashMap继承自HashMap,它在HashMap的基础上保留了插入元素的顺序。它使用链表来维护插入元素的顺序,所以可以按照插入的顺序输出元素。LinkedHashMap适合用于需要保持元素插入顺序的场景,例如LRU缓存。 IdentityHashMap:IdentityHashMap是一种特殊的HashMap,它使用对象的身份(即对象的引用)作为键的判断依据,而不是依靠 equals() 方法。它的特点是可以处理相同的键对象,因为它使用引用相等性来判断键的唯一性。IdentityHashMap适合用于需要处理相同键对象的场景,例如对象的标识映射。 ConcurrentHashMapConcurrentHashMap是一种线程安全的HashMap实现,它支持高并发的读写操作。它通过将数据分成多个段(segment)来实现并发控制,每个段有自己的锁来保护并发访问。ConcurrentHashMap适合用于需要高并发读写的场景,可以用于替代传统的同步机制(例如synchronized或ReentrantLock)。 综上所述,不同的Map实现有不同的特点和适用场景。选择合适的Map实现要根据具体的业务需求和性能要求进行考虑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值