LinkedHashMap源码分析

目录

  • 概述
  • 如何保证有序(敲黑板了)
    • 1. 扩展了HashMap的Node内部类
    • 2. 重写了newNode和newTreeNode方法
    • 3. 重写了HashMap中的三个回调方法
      • 3-1. afterNodeAccess
      • 3-2. afterNodeInsertion
      • 3-3. afterNodeRemoval
  • 遍历
    • HashMap的遍历思想
    • LinkedHashMap的遍历思想
  • 总结

概述

LinkedHashMap还是比较简单的, 相对于HashMap, 它是有序的, 那么问题就来了, 它是怎么保持有序的? 它直接继承于HashMap, 重写或增加了一些新的关于保持Map有序的方法, 至于扩容或是数据结构等都于HashMap一样, 下面我们重点分析它是怎么保持有序的这问题.

此外, LinkedHashMap还提供了按照最近访问有序还是按照插入顺序有序(属性: accessOrder), 默认为false(插入顺序有序), 可以通过LinkedHashMap的构造方法将其改为true(最近访问有序).

如何保证有序(敲黑板了)

想要保证有序, 那么元素之间就得有关系, 比如LinkedList, LinkedHashMap的思想也是同理, 只不过它在保证HashMap的数据结构下, 增加了元素之间的联系.

它通过以下几点保证了有序:

  1. 扩展了HashMap的Node内部类, 增加了before,after两个属性
  2. 重写了newNode方法
  3. 重写了HashMap中的三个回调方法

1. 扩展了HashMap的Node内部类

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

增加了两个属性, 使用这两个属性可以保持元素之间的关系.

有一个问题就是我们重写了HashMap的Node类, 那么HashMap在链表转为树的时候会不会出现类转换异常呢?

答案就在HashMap的TreeNode类上, 它继承了LinkedHashMap.Entry, 所以No problem.

2. 重写了newNode和newTreeNode方法

在HashMap的putVal方法中, 新增元素必然需要创建节点, 这里重写了HashMap的newNode方法, 并且调用了内部方法(linkNodeLast), 来维护新增元素与原集合中最后一个元素的关系.

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(p为集合中第一个节点)
    if (last == null) {
        // head也为p
        head = p;
    } else {
        // 使p的上一个元素为原集合中的最后一个元素
        p.before = last;

        // 原集合中最后一个元素的下一个元素为p
        last.after = p;
    }
}

是不是So Easy, 就是维护链表之间的关系.

3. 重写了HashMap中的三个回调方法

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

通过注释得知, 这方法的定义是为了让LinkedHashMap进行回调使用的.

这三个方法综合来说, 就是为了维护在发生元素的变动时, 对他们之间的关系进行维护.

3-1. afterNodeAccess

目的是将最后访问的元素移动到last, 注意: 只是改变Node之间的连接关系, 并不会改变元素的位置.

通过方法名可知, 这是在元素进行访问之后进行调用的.

源码如下:

void afterNodeAccess(Node<K,V> e) { // move node to last

    LinkedHashMap.Entry<K,V> last;

    // accessOrder默认是false, 可以通过构造方法进行设置为true.
    // 并且当前访问的元素不是last
    if (accessOrder && (last = tail) != e) {

        // 获取当前元素, 其前驱以及其后继
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;

        // 因为要把当前元素置为last, 所以它的after为null.
        p.after = null;

        // 当前元素没有前驱, 所以head为它的后继
        if (b == null)
            head = a;
        else  // 否则, 其前驱的after为其后继
            b.after = a;

        // 后继不为null的话, 后继的前驱即是其前驱.
        if (a != null)
            a.before = b;
        else // 既然 (last = tail) != e 成立了, 那么这里的a应该不会出现为null的情况.
            last = b;

        //
        if (last == null)
            head = p;
        else {
            // 将 last 和 p 进行互换
            p.before = last;
            last.after = p;
        }

        // 令tail为p, 最后访问的元素
        tail = p;
        ++modCount;
    }
}

3-2. afterNodeInsertion

这里是在put元素之后发生的调用, 似乎永远不会进行处理.

源码如下:

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

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

通过源码发现, removeEldestEntry这个方法永远返回false, 所以条件永远不会成立, 所以nothing.

3-3. 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的遍历思想和LinkedHashMap的遍历思想是不同的.

HashMap的遍历思想

在数组上从0下标开始遍历, 如果是null, 则+1, 去下一个位置查找元素; 如果不是null, 返回该元素, 然后如果该位置有链表或者红黑树时, 会进入到链表或者红黑树中查找元素返回.

LinkedHashMap的遍历思想

从head开始遍历, 也就是头结点元素, 通过元素之间的关系, 一直遍历到尾结点tail.

显然是链表的思想.

总结

  1. LinkedHashMap是对HashMap的扩展, 增加有序的实现.
  2. 提供插入顺序排序和最近访问排序

不要因为知识简单就忽略, 不积跬步无以至千里.

原文地址:https://www.cnblogs.com/wuqinglong/p/9642016.html

时间: 2024-10-06 09:56:38

LinkedHashMap源码分析的相关文章

LinkedHashMap 源码分析

LinkedHashMap 源码分析 1. 基本结构 1. 实现 实现的接口是 Map 2. 继承 ?? 继承的是 HashMap 这个就比较熟悉了,事实上我们会看到 LinkedHashMap 代码量非常的少,主要就是因为他继承的 HashMap ,继承了大多数的操作. 仔细一点的都会发现 HashMap 里面有非常多的空白方法,这些方法其实是模板方法,为了让继承 HashMap 的类重写一些自己的特性.而不破坏代码结构. 3. 数据域 1. 基本字段 ?? 在 HashMap 的基础上他添加

Java进阶之----LinkedHashMap源码分析

最近事情有点多,今天抽出时间来看看LinkedHashMap的源码,其实一开始是想分析TreeMap来这,但是看了看源代码之后,决定还是等过几天再分析,原因是TreeMap涉及到了树的操作..而之前没有接触过树的这种数据结构,只是在学校学一点皮毛而已..所以我还是打算过几天先恶补一下相关的知识再来对TreeMap做分析. 言归正传,我们今天来看LinkedHashMap.从名字上我们可以看出来,这个对插入的值是保持顺序的,即我们插入的顺序就是我们输出的顺序,如果不相信,我们可以用HashMap和

Java集合之LinkedHashMap源码分析

简介 LinkedHashMap内部维护了一个双向链表,能保证元素按插入的顺序访问,也能以访问顺序访问,可以用来实现LRU缓存策略. LinkedHashMap可以看成是 LinkedList + HashMap. 继承体系 LinkedHashMap继承HashMap,拥有HashMap的所有特性,并且额外增加了按一定顺序访问的特性. 存储结构 我们知道HashMap使用(数组 + 单链表 + 红黑树)的存储结构,那LinkedHashMap是怎么存储的呢? 通过上面的继承体系,我们知道它继承

LinkedHashMap源码分析与LRU实现

LinkedHashMap可认为是哈希表和链接列表综合实现,并允许使用null值和null键.LinkedHashMap实现与HashMap的不同之处在于,LinkedHashMap维护着一个运行于所有条目的双重链接列表.此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序. LinkedHashMap的实现不是同步的.如果多个线程同时访问LinkedHashMap,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步. 1.LinkedHashMap的存储结构   Link

Java集合(13)--LinkedHashMap源码分析

HashMap使用哈希表来存储数据,并用拉链法来处理冲突.LinkedHashMap继承自HashMap,同时自身有一个链表,使用链表存储数据,不存在冲突. LinkedList和LinkedHashMap一样使用一个双向循环链表,但LinkedList存储的是简单的数据,并不是“键值对”. LinkedList和LinkedHashMap都可以维护内容的顺序,但HashMap不维护顺序. public class LinkedHashMap<K,V> extends HashMap<K

JDK1.8源码分析之LinkedHashMap(二)

一.前言 前面我们已经分析了HashMap的源码,已经知道了HashMap可以用在哪种场合,如果这样一种情形,我们需要按照元素插入的顺序来访问元素,此时,LinkedHashMap就派上用场了,它保存着元素插入的顺序,并且可以按照我们插入的顺序进行访问. 二.LinkedHashMap用法 import java.util.Map; import java.util.LinkedHashMap; public class Test { public static void main(String

死磕 java集合之LinkedHashSet源码分析

问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashSet支持按元素访问顺序排序吗? 简介 上一节我们说HashSet中的元素是无序的,那么有没有什么办法保证Set中的元素是有序的呢? 答案是当然可以. 我们今天的主角LinkedHashSet就有这个功能,它是怎么实现有序的呢?让我们来一起学习吧. 源码分析 LinkedHashSet继承自HashS

HashMap和LinkedHashMap的迭代器源码分析

一.前言 在遍历HashMap与LinkedHashMap时,我们通常都会使用到迭代器,而HashMap的迭代器与LinkedHashMap迭代器是如何工作的呢?下面我们来一起分析分析. 二.迭代器继承图 三.HashMap迭代器 3.1 HashIterator HashIterator是一个抽象类,封装了迭代器内部工作的一些操作. HashIterator类属性 abstract class HashIterator { // 下一个结点 Node<K,V> next; // next e

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu