java之LinkedHashMap源码详细解析

1. 定义特点

LinkedHashMap结合了hashmap和双向链表
LinkedHashMap是HashMap的子类,有着和HashMap的多数特性。

其特点大概有:

  • key和value都允许为空
  • key可重复可覆盖,value可重复
  • 有序的
  • 非线程安全的

还可实现LRU (最近最少使用)算法

通过查看源码 也可看到其这个类的大概意思:

  • 实现哈希表和链表的Map接口,具有可预测的迭代顺序。

  • 它维护一个贯穿其所有条目的双链接列表。 这个链表定义了迭代顺序,这通常是键插入到映射中的顺序(插入顺序)。

  • 这个类提供了所有可选的Map操作,并允许空元素。 与HashMap一样,它为基本操作(添加、包含和删除)提供了常量时间性能,前提是哈希函数将元素正确地分散在存储桶中。 由于维护链表的额外开销,性能可能略低于HashMap,但有一个例外:对LinkedHashMap的集合视图进行迭代所需的时间与map的大小成比例,而与map的容量无关。 在HashMap上迭代可能更昂贵,需要的时间与其容量成正比。

  • 实现不是同步的。 如果多个线程并发地访问一个链接的哈希映射,并且至少有一个线程在结构上修改了这个映射,那么它必须在外部进行同步。 这通常是通过对自然封装了映射的对象进行同步来实现的。 如果不存在这样的对象,映射应该使用集合进行“包装”。 synchronizedMap方法。 这最好在创建时完成,以防止意外的不同步访问地图:
    Map m =集合。 synchronizedMap(新LinkedHashMap(…));

  • 结构修改是添加或删除一个或多个映射的任何操作,或者在访问顺序链接哈希映射的情况下,影响迭代顺序。 在插入顺序的链接哈希映射中,仅仅更改与已经包含在映射中的键相关联的值并不是结构修改。 在访问顺序链接哈希映射中,仅仅使用get查询映射就是对结构的修改。)

以下源码主要是jdk1.8以上的

2. 源码

类结构的具体定义

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

多了双链表的定义
也就是双向链表的头尾节点以及访问的迭代顺序(布尔值表示正序与反序)

  • header是LinkedHashMap所维护的双向链表的头结点
  • tail是尾节点
  • accessOrder用于决定具体的迭代顺序

尾结点的后继其实也指向了header,所以就是双向循环链表

/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;

/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;

/**
 * The iteration ordering method for this linked hash map: {@code true}
 * for access-order, {@code false} for insertion-order.
 *
 * @serial
 */
final boolean accessOrder;

五个构造函数:

  • 四个构造方法都将accessOrder设为false,默认是插入顺序排序的
  • 第五个构造方法自定义传入的accessOrder的值,指定双向循环链表中排序规则
/*用指定的初始容量和负载因子构造一个空的按插入顺序排列的LinkedHashMap实例。  
参数:  
initialCapacity—初始容量  
loadFactor—负载因子  
抛出:  
IllegalArgumentException -如果初始容量是负的或者负载系数是非正的*/  
public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

/*用指定的初始容量和默认负载因子(0.75)构造一个空的按插入顺序排列的LinkedHashMap实例。  
参数:  
initialCapacity—初始容量  
抛出:  
IllegalArgumentException -如果初始容量为负数  */
public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

/*Constructs an empty insertion-ordered LinkedHashMap instance with the default initial capacity (16) and load factor (0.75).*/
public LinkedHashMap() {
        super();
        accessOrder = false;
    }

/*用与指定映射相同的映射构造一个按插入顺序排列的LinkedHashMap实例。 LinkedHashMap实例是用一个默认的负载因子(0.75)和一个初始容量创建的,该容量足以容纳指定映射中的映射。  
参数:  
M -该地图的映射将被放置在该地图中  
抛出:  
NullPointerException -如果指定的映射为空  */
public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

/*用指定的初始容量、负载因子和排序模式构造一个空的LinkedHashMap实例。  
参数:  
initialCapacity—初始容量  
loadFactor—负载因子  
accessOrder -排序模式-访问顺序为true,插入顺序为false  
抛出:  
IllegalArgumentException -如果初始容量是负的或者负载系数是非正的  */
public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

其基本结构entry
before、after是用于维护Entry插入的先后顺序的.

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

维护链表的操作:

删除,插入,获取节点之后,对链表进行维护

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

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

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

get操作 具体和hashmap的get操作大致意思

返回指定键映射到的值,如果该映射不包含键的映射,则为空。
更正式地说,如果这个映射包含一个从键k到值v的映射,这样(key==null ? K ==null: key.equals(K)),然后这个方法返回v; 否则返回null。 (这样的映射最多只能有一个。)
返回值为null并不一定表明映射没有包含键的映射; 也有可能映射显式地将键映射为空。 可以使用containsKey操作来区分这两种情况。

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

其他的put remove操作大致相通,都是在双链表的基础上操作
对此可以配合lru算法进行结合
关于lru,面试题可是高频题

3. 与LRU(最近最少使用算法)结合

用LinkedHashMap实现LRU算法时,构造方法需要将accessOrder的值设置为true

在这里插入图片描述

这部分代码来源于leetcode
链接如下:
lru缓存机制

class LRUCache extends LinkedHashMap<Integer, Integer>{
    private int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75F, true);
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity; 
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农研究僧

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值