简单介绍
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>{
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder;
}
可以看到LinkedHashMap是hashmap的子类,所以一些方法的相关实现是基于hashmap的,同时底层是由entry节点构成的双向链表。另外,这里的accessOrder变量相当重要,是区分hashmap和linkedhashMap的关键。
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);
}
}
LinkedHashMap中有entry类继承自hashmap中的Node类。entry就是LinkedHashMap中的基本节点组成,其实就是双向链表的节点。
有序性
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
可以看到,其实就是调用了hashmap的方法,不过在最后添加了一个afterNodeAccess方法,这方法是干嘛的呢?好记得我们上边说过的accessOrder吧,它开始起作用了
这个accesOrder其实就是控制节点在linkedHashmap
- 如果是true, 基于访问顺序
- 如果是false,就按插入顺序排列
什么是访问顺序和插入顺序呢?
基于插入顺序:
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
map.put("1", "a");
map.put("2", "b");
map.put("3", "c");
map.put("4", "e");
for (Iterator<String> iterator = map.values().iterator(); iterator
.hasNext();) {
String name = (String) iterator.next();
System.out.print(name);
}
}
abce
基于访问顺序
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
map.put("1", "a");
map.put("2", "b");
map.put("3", "c");
map.put("4", "e");
//new add
map.get("1");
map.get("2");
for (Iterator<String> iterator = map.values().iterator(); iterator
.hasNext();) {
String name = (String) iterator.next();
System.out.print(name);
}
}
ceab
插入顺序很好理解,访问顺序其实就是把最近访问过的元素按照访问顺序依次放入数组后边,即所谓的LRU算法。
有序性的维护
那么linkedhahsmap为什么可以实现有序性呢?
我们来看下插入元素的过程,看下这个有序性是怎么维护的,linkedhashmap并没有put函数,别忘了,他是hashmap的子类,所以实际上用的父类的put函数,回过头去看hashmap中的put,我们插入元素的时候,如果hashmap中不存在对应节点key,我们就会新建节点,奥秘就在新建节点,我们来看下linkedhashmap中怎么新建节点
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;
}
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;
}
}
函数很好理解,基本就是新建一个节点,然后把该节点接到链表尾部
,如此保证插入有序的维护。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 一系列插入操作
if (e != null) { // existing mapping for key
afterNodeAccess(e);
return oldValue;
}
}
}
void afterNodeAccess(Node<K,V> p) { }
hashmap中的put在插入完毕后提供了两个后置处理函数afterNodeAccess丢给linkedhashmap去处理
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
// 查到的节点赋给p
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时候才起作用,换言之,只有按访问顺序查找时该方法才起作用。这个方法在getNode()时也会调用,换句话说,只要使用到了节点,就会调用这个方法把你所使用过的节点放入链表尾部,依次来维护访问排序。
总结
linkedhashmap底层借助哈希桶+双向链表,就是在hashmap的基础上通过双向链表维护元素节点间的顺序。如果想要查找效率快且维护插入顺序的话,用这玩意儿就对啦,默认的就是按插入顺序排列。
公众号:程序员二狗
每日原创 交流学习