LinkedHashMap是数组+双向链表的数据结构,继承HashMap并重写部分方法来达到双向链表的特性。
LindedHashMap重写了HashMap的Entry,使其具有双向链表的特性,自身维护的属性只有两个属性,其他均继承自HashMap。
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
private final boolean accessOrder;
构造方法
LinkedHashMap的构造方法都是调用父类即HashMap中相应的构造进行初始化,唯一不同的是在HashMap中的init()没有作用,而LinkedHashMap重写了该方法进行初始化双向链表的特性。
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
这几个构造就不做分析了,看下init()方法
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
这里初始化了一个空的头节点header。before和after都指向自己。
put浅析
LinkedHashMap中没有重写put,采用父类即HashMap中的方法,但对其中的方法进行重写使其适用自身特性。
关于HashMap的分析可以参考HashMap浅析
- addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
//调用父类方法进行数据添加,涉及一些方法重新往下看
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
- transfer
void transfer(HashMap.Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
//遍历整个双向链表进行数据移动
for (Entry<K,V> e = header.after; e != header; e = e.after) {
if (rehash)
e.hash = (e.key == null) ? 0 : hash(e.key);
int index = indexFor(e.hash, newCapacity);
//注意这里是next而不是after
e.next = newTable[index];
newTable[index] = e;
}
}
举例讲解下上述代码的过程。如图中第一行header、Entry(B)、Entry©,第二行为新数组的下边位置(下边为重新计算)
- 首先for循环将header的after节点即B给e(中间变量),然后判断是否为header节点,不是则进入循环内部。
- 重新计算index;
- 将e.next指向新数组的对应下边位置,即图中红色箭头部分;
- 将新数组index位置赋值e即B;
- 再循环将e.after即C指定变量e,注意此处是e.after而不是e.next;
- 再进行以上几步操作,最终会生成黑色箭头的链表能够串起来
从网上找了一个图片,供大家理解
- createEntry
void createEntry(int hash, K key, V value, int bucketIndex) {
//将当前位置数据放到中间变量old中。
HashMap.Entry<K,V> old = table[bucketIndex];
//新创建并将old放到e.next上
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
//将新增加的节点放到链表的尾部
e.addBefore(header);
size++;
}
//将新节点放到existingEntry(header)前边即链表的尾部
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
get浅析
相较于put,get方式就比较简单了,LinkedHashMap重写了get方法
public V get(Object key) {
//调用父类的获取如果有则返回,若没有则返回null
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
此时存在两种情况:
- key对应的value为null;
- Map中没有对应的值;
总结
HashMap与LinkedHashMap的区别:
- HashMap是数组+单链表的方式,LinkedHashMap是数组+双向链表;
- HashMap是无序的,LinkedHashMap是有序的;
- HashMap查询效率快,插入效率慢,LinkedHashMap是插入效率快,查询效率慢;
以上是个人的理解,如有不妥也请各位大咖指正。