LinkedHashMap常用方法源码

类介绍(注释)

  1. add、contains、remove 方法,时间复杂度是O(1)。
  2. LinkedHashMap的遍历耗时,与_capacity无关,与map的size(元素多少)呈线性。_
  3. HashMap的遍历,可能比_LinkedHashMap更耗时,其和_capacity呈线性关系。
  4. LinkedHashMap是非线程安全的,并发出错时,会快速失败,抛出ConcurrentModificationException。可以使用_Collections.synchronizedMap(new LinkedHashMap(…));_
  5. LinkedHashMap非常适合于构建LRU缓存least-recently Used)。
  6. 并不是所有的adds、delete操作都会造成LinkedHashMap结构的变更。

_insertion-ordered 模式下,_修改一个已经存在的key,对应的值,并不会造成结构的变更
access-ordered 模式下,get将造成结构的变更

LinkedHashMap的一些概念


// 头结点,同时也是最早插入的节点。
transient LinkedHashMap.Entry<K,V> head;

// 尾结点,同时也是最后插入的节点。
transient LinkedHashMap.Entry<K,V> tail;


// 继承 Node,为数组的每个元素增加了 before 和 after 属性。
// LinkedHashMap 的数据结构很像是把 LinkedList 的每个元素换成了 HashMap 的 Node,像是两者的结合体.
// 也正是因为增加了这些结构,从而能把 Map 的元素都串联起来,形成一个链表,而链表就可以保证顺序了,就可以维护元素插入进来的顺序。
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);
    }
}

// 控制两种访问模式的字段,默认 false
// true 按照访问顺序,会把经常访问的 key 放到队尾
// false 按照插入顺序提供访问
final boolean accessOrder;

常用方法分析

LinkedHashMap() (无参构造方法)

LinkedHashMap 在无参构造方法中,将accessOrder设置为false,即按照插入顺序访问。它的_capacity、与HashMap的默认的一致,为16、0.75。

public LinkedHashMap() {
    super();
    accessOrder = false;
}

LinkedHashMap按顺序插入节点(put)

首先,put是直接调用HashMap#put方法。从LinkedHashMap 的重要概念中,可以看到,存在一个继承了HashMap.Node的Entry。
当前put时,即会创建LinkedHashMap#Entry对象。并且,LinkedHashMap中重写了HashMap#put中调用的newNodeafterNodeInsertionafterNodeAccess方法。
put的具体流程,可以参照HashMap源码。这里来看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;
}

// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    // 备份尾节点
    LinkedHashMap.Entry<K,V> last = tail;
    // 将新插入的节点,作为尾节点
    tail = p;
    
    // 如果原先尾节点为null(原先链表为空,现在有一个元素)
    if (last == null)
        // 将p也作为头结点(只有一个元素的链表,head=tail=唯一的元素)
        head = p;
    else {
        // 链表有数据,则建立 前后 指向
        p.before = last;
        last.after = p;
    }
}

LinkedHashMap 通过新尾节点,给每个节点增加 before、after 属性,每次新增时,都把节点追加到尾节点等手段,在新增的时候,就已经维护了按照插入顺序的链表结构了。

遍历

LinkedHashMap可以通过以下,实现的Map接口中的方法,进行遍历。
image.png
当然,也可以通过迭代器来进行遍历。如map.entrySet().iterator();得到迭代器,再通过hasNext方法判断是否有下一个节点后,最后通过next来访问下一个节点。
其中的实现都依赖LinkedHashIterator。

final class LinkedEntryIterator extends LinkedHashIterator implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { 
        return nextNode(); 
    }
}

// 下个节点
LinkedHashMap.Entry<K,V> next;
// 当前节点
LinkedHashMap.Entry<K,V> current;
// 期望的结构版本号
int expectedModCount;

// 构造方法
LinkedHashIterator() {
    // 初始化时,头结点即为 next
    next = head;
    expectedModCount = modCount;
    current = null;
}

// 是否还有下个节点
public final boolean hasNext() {
    return next != null;
}

// 获取下个节点
final LinkedHashMap.Entry<K,V> nextNode() {
    LinkedHashMap.Entry<K,V> e = next;
    
    // 判断结构版本号 是否有变更。有变更的话抛出错误
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    
    // 没有下个节点
    if (e == null)
        throw new NoSuchElementException();
    
    // 有下个节点,通过e.after,取得下个节点,并赋值给next
    current = e;
    next = e.after;
    
    // 返回当前节点
    return e;
}

LRU 最近最少使用

使用示例

这里,是采用了LinkedHashMap的三个参数的构造方法,其源码如下。其中最主要的,是指定了accessOrdertrue (默认为false)。 true 按照访问顺序,会把经常访问的 key 放到队尾,get时会造成结构的变更; false 按照插入顺序提供访问。

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

LRU大概的意思就是经常访问的元素会被追加到队尾,这样不经常访问的数据自然就靠近队头,然后我们可以通过设置删除策略,比如当 Map 元素个数大于3时。

public static void main(String[] args) {
    // 初始化时,主要指定了accessOrder为true
    LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(16,0.75f,true) {

        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
            return this.size() > 3;
        }
    };

    // 放入元素
    map.put("1",1);
    map.put("2",2);
    map.put("3",3);
    map.put("4",4);

    // 输出,并测试LRU策略
    System.out.println(map);
    map.get("2");
    System.out.println(map);
    map.get("3");
    System.out.println(map);
    map.get("4");
    System.out.println(map);
}
// {2=2, 3=3, 4=4}
// {3=3, 4=4, 2=2}
// {4=4, 2=2, 3=3}
// {2=2, 3=3, 4=4}

可以看到:我们往map中放置四个元素,但输出结果只有三个元素。1 不见了,这个是因为在每次put时,会调用afterNodeInsertion方法。该方法在允许删除节点时,并在removeEldestEntry方法返回true的情况下,(我们在用例中重写了removeEldestEntry方法)会移除头节点。源码如下:

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

当我们调用get方法后,发现输出的元素顺序发生改变。被访问元素移动到链表尾部,这个体现了最经常被访问的节点会被移动到链表尾部。在调用get后,头节点即为最早插入-最少被访问的节点。

get方法源码

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    
    // accessOrder为true时,才会去将访问的元素,放到链表尾部
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LinkedHashMap 是 HashMap 的子类,它在 HashMap 的基础上增加了按照插入顺序或者访问顺序来迭代元素的能力。LinkedHashMap常用方法包括: 1. put(key, value):向 map 中添加一个映射关系。 2. get(key):获取指定 key 的 value。 3. remove(key):根据 key 删除映射关系。 4. clear():清空 map 中的所有映射关系。 5. size():返回 map 中映射关系的数量。 6. containsKey(key):判断是否包含指定的 key。 7. containsValue(value):判断是否包含指定的 value。 8. keySet():返回 map 中所有 key 的集合。 9. values():返回 map 中所有 value 的集合。 10. entrySet():返回 map 中所有映射关系的集合。 11. getOrDefault(key, defaultValue):获取指定 key 的 value,如果不存在则返回 defaultValue。 12. forEach(action):对 map 中每个映射关系执行 action 操作。 13. replace(key, oldValue, newValue):替换指定 key 的 oldValue 为 newValue。 14. putIfAbsent(key, value):如果 map 中不存在指定 key 的映射关系,则添加一个映射关系。 15. computeIfAbsent(key, mappingFunction):如果 map 中不存在指定 key 的映射关系,则根据 mappingFunction 的计算结果添加一个映射关系。 16. computeIfPresent(key, remappingFunction):如果 map 中存在指定 key 的映射关系,则根据 remappingFunction 的计算结果更新映射关系。 17. merge(key, value, remappingFunction):将指定 key 的 value 与指定值合并,并根据 remappingFunction 的计算结果更新映射关系。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值