观看该篇文章时,请先了解HashMap的底层原理!
LinkedHashM定义:
哈希表和链表实现的映射接口,具有可预测的迭代顺序。这个实现不同于HashMap,因为它维护一个遍历其所有条目的双向链表。这个链表定义了迭代顺序,这通常是键插入到映射中的顺序(插入顺序)。如果键是已经插入过的,则会直接返回true。
先给张大概的原理图:
LinkedHashMap每一次存值时,都会往链表的末尾添加数据。
类的继承关系
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
由上可知,LinkedHashMap直接继承了HashMap,所以拥有HashMap所有的性质。
主要成员变量:
accessOrder:这个链表HashMpa的迭代排序方法。true:使用请求顺序,false:使用插入顺序。
插入顺序:即抵用put和putAll方法时,根据插入顺序将数据存入双向链表中。
例子:
Map<Object,String> map = new LinkedHashMap();
Map hashMap = new HashMap();
map.put("1","a");
map.put("2","b");
map.put("3","c");
Iterator iter1 = map.entrySet().iterator();
while(iter1.hasNext()){
Map.Entry entry = (Map.Entry)iter1.next();
System.out.println("插入"+entry.getValue());
}
输出结果:
插入a
插入b
插入c
请求顺序:调用get方法时,将被请求的数据,挪到双向链表的最前面。
例子:
Map<Object,String> map = new LinkedHashMap(16,0.75F,true);
map.put("1","a");
map.put("2","b");
map.put("3","c");
Iterator iter1 = map.entrySet().iterator();
while(iter1.hasNext()){
Map.Entry entry = (Map.Entry)iter1.next();
System.out.println("前"+entry.getValue());
}
map.get("1");
Iterator iter2 = map.entrySet().iterator();
while(iter2.hasNext()){
Map.Entry entry = (Map.Entry)iter2.next();
System.out.println("后"+entry.getValue());
}
输出结果:
前a
前b
前c
后b
后c
后a
看输出结果可知,请求的数据会被重新排序到双向链表最后。
链表结点对象
同时关注一下LinkedHashMap中的链表Entry对象,该对象继承与HashMap的Entry,并加了两个独有属性before和after
Entry
before:存在自己插入之前插入的条目
after:关联在自己插入之后插入的条目。
hash:和HashMap中用法一样,存该key的hash
key:和HashMap中用法一样,存key
value:和HashMap中用法一样,存value。
next:和HashMap中用法一样,存该数组结点链表的下一个条目。
构造方法
1
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
2
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
3
public LinkedHashMap() {
super();
accessOrder = false;
}
4
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
5
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
看上诉构造函数可知,每个构造函数都调用了父类HashMap的构造函数。而HashMap有四个构造函数,且另外三个构造函数都会调用带有三个参数的构造方法。在该方法中,调用init()方法,在HashMap中,init()方法中没有任何实现,那么调用这个方法就是为了给子类留出一个入口,让子类通过该路口完成一些初始化工作。
创建LinkedHashMap
显而易见,LinkedHashMap重写了init()方法。
void init() {
//双向链表的头结点,没有存值。
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
init()方法初始化了双向链表的头结点,注意该结点永远都不会存值。
put一个key-value
LinkedHashMap并没有重写put()方法,直接使用的是HashMap中的put()方法。
HashMap的put()方法中,调用了addEntry()方法,LinkedHashMap重写了该方法。
void addEntry(int hash, K key, V value, int bucketIndex) {
//直接调用父类即HashMap的addEntry()方法,给底层数组添加结点。
super.addEntry(hash, key, value, bucketIndex);
//removeEldestEntry(Entry<K,V> entry)该方法返回boolean值,LinkedHashMap默认返回false。我们可以重写该方法控制返回的true和false从而控制双向链表的条目的删除。
我们可以重写该方法控制返回的true和false从而控制双向链表的条目的删除。
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
//删除指定key的值
removeEntryForKey(eldest.key);
}
}
这里再说一下removeEldestEntry(Entry<K,V> entry)。
在源码的注解中,举了一个例子
private static final int MAX_ENTRIES = 100;
重写了removeEldestEntry方法,当双向链表的结点大于100时,把最老的数据先删除。
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
在HashMap的addEntry()方法中,会调用createEntry()方法,LinkedHashMap也重写了这个方法
void createEntry(int hash, K key, V value, int bucketIndex) {
/*在table(底层数组)中,添加该结点*/
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
其他逻辑都和HashMap一样,需要注意的是e.addBefore(header);该方法就是将条目添加到双向链表里的入口。
private void addBefore(Entry<K,V> existingEntry) {
/*将新结点关联到链表的before元素中*/
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
LinkedHashMap的put方法就是这个流程。
get一个key-value
与put()不同,LinkedHashMap的重写了get()方法
public V get(Object key) {
/*调用HashMap中的get()方法,查找到该结点*/
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
/*根据accessOrder,如果是true,即请求顺序,则将get的值挪到集合的最后一位,如果为false,即插入顺序,则什么都不做*/
e.recordAccess(this);
return e.value;
}
再细看看recordAccess(this);方法
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
看上诉代码可知,如果是请求顺序,则通过删除和添加将双向链表中的该结点挪到双向链表的before里。
put和get大部分逻辑都和HashMap几乎一样,只是多了维护双向链表的步骤而已。只要注意双向链表的排序顺序是请求顺序还是插入顺序!
删除remove方法
LinkedHashMap并没有看见remove(Object key)方法。
只有Entry内部类里有一个remove()方法。
private void remove() {
before.after = after;
after.before = before;
}
但是我们平常使用的时候直接linkedHashMap.remove()就可以了,并没有linkedHashMap.Entry.remove()这么调用,所以我们调用的remove()其实是父类HashMap的,通过HashMap的remove()方法,调用了Entry的recordRemoval(HashMap<K,V> m)方法,LinkedHashMap的Entry重写了该方法。
void recordRemoval(HashMap<K,V> m) {
remove();
}
并且调用Entry中的remove()方法;
private void remove() {
before.after = after;
after.before = before;
}
通过这个方法删除了双向链表中的值。
如果你对HashMap已经很了解了,LinkedHashMap还是很好理解的!