【Java】LinkedHashMap 解析

291 阅读3分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

一、前言

如果了解 HashMap,那么 LinkedHashMap 就很容易上手,LinkedHashMapHashMap 的一个子类。

HashMap 插入 key-value 数据,但遍历时候并不是按照插入的顺序来,如下 demo

public class Test {

    public static void main(String[] args) {

        Map<Integer, String> map = new HashMap<>();
        map.put(2, "牛yyds");
        map.put(1, "哈xswl");
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry);
        }
    }
}

输出如下:

1=哈xswl
2=牛yyds

LinkedHashMap 会记录插入 key-value 的顺序,遍历时候会按照顺序输出,demo 如下:

public class Test {

    public static void main(String[] args) {

        Map<Integer, String> map = new LinkedHashMap<>();
        map.put(2, "牛yyds");
        map.put(1, "哈xswl");
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry);
        }
    }
}

输出如下:

2=牛yyds
1=哈xswl

LinkedHashMap 区别于 HashMap,他会记录一下 key-value 顺序,用一个链表来记录。

举个栗子:在调用 LinkedHashMapput() 方法时,实际上调用的是 HashMapput()

  • 先调用 HashMapput() 方法
  • 再调用 newNode() 方法
// 源码如下:HashMap
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) {
    // 一大堆操作
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 调用子类方法
        tab[i] = newNode(hash, key, value, null);
    
    // ... ...
    afterNodeInsertion(evict);
    return null;
}
// LinkedHashMap 的 方法:newNode()
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);
    // 在双向链表的表尾增加一个
    linkNodeLast(p);
    return p;
}

LinkedHashMap 一些特性:

问题结论
是否允许为空KeyValue 都允许空
是否允许重复数据Key 重复会覆盖、Value 允许重复
是否有序有序
是否线程安全非线程安全



二、其他操作

LinkedHashMap 最主要的是维护了一个双向链表。

主要操作有:

  • 设置 valueput()
  • 移除 valueremove()

1. put() 操作

LinkedHashMap 调用 put() 是调用 父类 HashMap 的方法。

默认情况下,多次做 key 值覆盖,是不会改变顺序的。

因为,LinkedHashMap 有一个参数:accessOrder,默认为 false

  • 如果 accessOrder = false : get()put() 操作,对应 key 都不会改变其在链表里的顺序
  • 如果 accessOrder = true : get()put() 操作,都会导致其在链表里的改变,会被挪到链表的尾部
public class Test {

    public static void main(String[] args) {

        Map<Integer, String> map = new LinkedHashMap<>(16, 0.75f, true);
        map.put(2, "牛yyds");
        map.put(1, "哈xswl");
        map.put(3, "牛哇牛哇");
        map.put(2, "牛yyds-1");
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry);
        }
    }
}

输出结果如下:

1=哈xswl
3=牛哇牛哇
2=牛yyds-1

那这个参数 accessOrder 是如何影响的呢?

关键字:afterNodeAccess()

// LinkedHashMap 源码如下: 
public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        // 添加至末尾
        afterNodeAccess(e);
    return e.value;
}
    
// HashMap 源码如下:
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)
    // ... ...
    else {
        // ... ...
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // 添加至末尾
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // ... ...
}

2. remove() 操作

删除某个元素时候,当然需要将次元素从链表中删除。

过程当然类似喽:

  1. 先调用父类 Hashmapremove() 方法
  2. 调用子类 afterNodeRemoval() 方法

源码解析如下:

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

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 &&
        // ... ...
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            // ... ..
            // 调用子类方法
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}



// LinkedHashMap 源码如下:
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;
}