龙空技术网

Java集合系列-Map系列-LinkedHashMap源码解析

程序员面试工具箱 275

前言:

当前同学们对“双向链表的时间复杂度是什么”大致比较注意,看官们都需要剖析一些“双向链表的时间复杂度是什么”的相关资讯。那么小编同时在网上搜集了一些关于“双向链表的时间复杂度是什么””的相关文章,希望咱们能喜欢,你们快快来了解一下吧!

本人是工作7年的老程序员,在头条分享我对Java运用和源码、各种框架运用和源码的认识和理解,如果对您有所帮助,请持续关注。

声明:所有的文章都是自己工作之余一个字一个字码上去的,希望对学习Java的同学有所帮助,如果有理解不到位的地方,欢迎交流。

本文主要内容包括如下:

1:LinkedHashMap的demo2:结合demo对LinkedHashMap源码进行解析
第一节:LinkedHashMap的demo

demo代码如下:

public static void putDemo(){ Map<Integer,Integer> hashMap = new HashMap<>(); hashMap.put(1,1); hashMap.put(10,2); hashMap.put(3,3); hashMap.put(4,4); System.out.println("HashMap的运行结果如下:"); hashMap.forEach((key,value)-> System.out.println(key+"::"+value)); Map<Integer,Integer> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put(1,1); linkedHashMap.put(10,2); linkedHashMap.put(3,3); linkedHashMap.put(4,4); System.out.println("LinkedHashMap的运行结果如下:"); linkedHashMap.forEach((key,value)-> System.out.println(key+"::"+value));}

运行结果如下图:

HashMap和LinkedHashMap运行结果比较

通过上面的运行结果可以看出,HashMap运行的结果不是元素插入的顺序,而LinkedHashMap运行的结果是元素插入的顺序,而LinkedHashMap继承了HashMap,前者输出的顺序就是插入的顺序,而后者输出的顺序却不是插入的顺序,它是怎样做到的呢?

第二节:LinkedHashMap源码解析

在第一节我说了LinkedHashMap是继承HashMap的,证据如下:

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

HashMap是通过(n-1)&hash(key)算出在数组的下标的,所以它并不一定按照插入的顺序输出,而LinkedHashMap是怎样处理呢,它继承HashMap,但又怎样按照插入的顺序输出的呢,我们接下来继续分析

在HashMap中把key-value封装成了Node数据结构,在LinkedHashMap中也对key-value进行了封装,它的数据结构如下:

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) { //直接调用HashMap的构造方法 super(hash, key, value, next); } }//双向链表的头结点transient LinkedHashMap.Entry<K,V> head;//双向链表的尾节点transient LinkedHashMap.Entry<K,V> tail;//这个属性在LinkedHashMap中很有意思//true:按照访问顺序迭代//false:按照插入顺序迭代final boolean accessOrder

从上面的代码我们大概能猜到它是通过双向链表来存储数据的,先插入的放到链表的前面,后插入的放到链表的后面,这样就能使输出顺序就是插入顺序。要验证这个说法,我们继续往下分析。

public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false;}public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false;}public LinkedHashMap() { super(); accessOrder = false;}从上面的构造方法可以看出,它都是调用HashMap的构造方法,accessOrder=false,说明迭代时都是按照插入顺序。

通过查找源码,我们并没有在LinkedHashMap中查找出put方法,说明它是直接调用父类HashMap的put方法的,但是如果完全按照HashMap的put方法,那迭代时就可能不是插入的顺序了,这不是说我们猜想的不正确吗?其实我们猜想的是没有什么问题的,接下来证明如下:大家看一下我从HashMap的put方法中截得一段代码如下:

在图中我标记的代码一:afterNodeInsertion和代码二:afterNodeAccess,在HashMap中都是空实现,而在LinkedHashMap中对这两个方法进行了重写,那么是不是这两个方法实现了LinkedHashMap的插入顺序的呢?接下来我们进入这两个方法看个究竟。

//因为LinkedHashMap调用的是父类HashMap的put方法,而在HashMap中evict=truevoid afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry<K,V> first;//removeEldestEntry=false,所以在put时不会进入此条件中 if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); }}//在LinkedHashMap中返回falseprotected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false;}

1

因为在上面这个方法中removeEldestEntry永远返回false,所以它永远进不去if条件中,我们在afterNodeInsertion方法中没有找到我们想要的结果,所以我们继续从afterNodeAccess方法中是否能找到我们的猜想。

void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; //因为accessOrder=false,所以永远也进不去这个条件中 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; }}

因为我在通过构造函数可知accessOrder=false,永远也进不去if条件中,所以在afterNodeAccess方法也没有验证我们的猜想,这就奇怪了,LinkedHashMap只有上面两个方法和HashMap的不一样,其余的put方法是一样呢,这怎么办呢?我们是不是漏掉了什么呢?我们在回过头来在仔细看一下put的源码

那么是不是把key-value封装成节点时做了什么文章呢?

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {//这段代码只是把key-value封装成一个Node节点,没有什么可看的 LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);//这段代码看着像我们找的结果 linkNodeLast(p); return p;}

linkNodeLast方法看着像对双向链表的操作,我么继续进入linkNodeLast方法中。

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; }}

分析了半天,终于找到了我们想要的结果,就是在插入新节点时同时操作了双向链表。

其实我开头并没有直接去分析newNode,而是先分析afterNodeInsert和afterNodeAccess,就是想告诉大家在查看源码去寻找我们想要的结果时,会和我们想象的不太一样,我们看着这段代码是我们想要的,其实不是,所以大家要有目的的看源码,这样才能更加的清晰。

LinkedHashMap在实际工作中用到的不是太多,它把Map的元素重新又用一个双向链表表示,我们知道双向链表的时间复杂度o(n),所以效率不是太高。

LinkedHashMap就给大家分享到这了,如果对它的源码感兴趣,请自行查看,都非常的简单。下一篇文章我给大家分析TreeMap,请大家持续关注。

标签: #双向链表的时间复杂度是什么