本人是工作7年的老程序员,发送我对Java运用和源码、各种框架运用和源码的认识和理解,如果对您有所帮助,请持续关注。
声明:所有的文章都是自己工作之余一个字一个字码上去的,希望对学习Java的同学有所帮助,如果有理解不到位的地方,欢迎交流。
本文主要内容包括如下:
1:LinkedHashMap的demo
2:结合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));
}
运行结果如下图:
Java集合系列-Map系列-LinkedHashMap源码解析
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方法中截得一段代码如下:
Java集合系列-Map系列-LinkedHashMap源码解析
在图中我标记的代码一:afterNodeInsertion和代码二:afterNodeAccess,在HashMap中都是空实现,而在LinkedHashMap中对这两个方法进行了重写,那么是不是这两个方法实现了LinkedHashMap的插入顺序的呢?接下来我们进入这两个方法看个究竟。
//因为LinkedHashMap调用的是父类HashMap的put方法,而在HashMap中evict=true
void 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中返回false
protected 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的源码
Java集合系列-Map系列-LinkedHashMap源码解析
那么是不是把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§;
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),所以效率不是太高。