LinkedHashMap是HashMap的一个子类,底层存储结构和HashMap一样,也是数组+Entry链表,不过这个Entry和HashMap中的Entry稍微有点区别,后面会详细介绍。put操作和get的大部分操作都是调用的HashMap方法来完成的.LinkedHashMap比HashMap多了什么功能呢?多了一个通过迭代器迭代所有元素时可以按照插入时的顺序进行迭代的功能。这个维持插入顺序的功能依赖了LinkedHashMap的一个header属性,这个属性是一个双向链表结构,迭代时就从header开始往后一个个的迭代。我们可以通过源码来一步步分析LinkedHashMap的主要功能。
先看类层次结构和主要属性
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
//双向链表的头 header.after就是第一个元素,header本身是不存元素的,java中的链表的header应该都是这种结构 傀儡节点
private transient Entry<K,V> header;
private final boolean accessOrder;
}
Entry是一个内部类,是HashMap.Entry的一个子类,是LinkedHashMap双向链表实现的数据结构,主要的代码如下:
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// 前驱和后继指针,为了迭代时保持插入顺序
Entry<K,V> before, after;
//调用HashMap.Entry的构造方法
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
//每次调用put方法新增元素时,在指定的元素前面添加元素
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
}
了解数据结构,存元素和取元素的方法必须要了解的,我们先看存元素的方法:put方法,这个方法是在父类HashMap中定义的,关于我的一篇HashMap文章:http://blog.youkuaiyun.com/wangjiang87/article/details/78669217,这里为了方便源码分析,还是把涉及到HashMap中的代码也复制了出来,put方法源码如下:
public V put(K key, V value) {
//null会存放到数组下标为0的位置
if (key == null)
return putForNullKey(value);
//根据hashCode算出一个hash值
int hash = hash(key.hashCode());
//根据hash和数组长度计算出元素要存放的下标
int i = indexFor(hash, table.length);
//这个循环是遍历下标i处的元素e,如果e.next不为null说明有hash冲突,形成了一个链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//hash值一样并且key一样,说明要放的元素之前已经放过了,这时就替换而不执行下面的addEntry方法
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//新创建一个Entry对象放到下标i处,这个方法在LinkedHashMap中进行了重写
addEntry(hash, key, value, i);
return null;
}
接着看addEntry方法在LinkedHashMap中是如何进行重写的,源码如下:
void addEntry(int hash, K key, V value, int bucketIndex) {
//根据参数创建一个Entry对象放到数组的bucketIndex处
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
//这个LinkedHashMap中直接返回的false,不需要删除最老的元素,有些实现LRU功能的LinkedeHashMap子类可能会重写此方法
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
//判断是否需要扩容
if (size >= threshold)
resize(2 * table.length);
}
}
接着分析addEntry方法中调用到的createEntry方法,看看这个方法中做了什么操作:
void createEntry(int hash, K key, V value, int bucketIndex) {
//先把指定下标处的元素取出来,如果hash冲突时,这个下标位置会已经有值,old元素会放在链表尾部,若hash没冲突,old肯定为null
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
//将新创建的Entry对象e放到指定下标处
table[bucketIndex] = e;
//将新创建的元素添加到头为header的这个链表的尾部,就是这个链表维护了元素的插入顺序,遍历的时候才能通过header一个个的按照插入顺序读取元素
e.addBefore(header);
size++;
}
addBefore方法是LinkedHashMap.Entry中的方法:
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
这个方法的作用就是在header之前插入一个元素,也即是在链表尾部插入元素,自己可以画下这个指针的指向情况。
以上方法就将LinkedHashMap存元素进行的操作方法介绍完了,取元素的get方法相对来说就简单多了,主要就调用了HashMap中的getEntry方法,这个可以参考HashMap文章中的介绍,这里就不在赘述了。
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
上面的如果都理解了,LinkedHashMap的迭代器也就容易理解多了。
private abstract class LinkedHashIterator<T> implements Iterator<T> {
//header.after是LinkedHashMap中的第一个元素,header是傀儡节点,不存放真正的数据
Entry<K,V> nextEntry = header.after;
Entry<K,V> lastReturned = null;
//用来快速失败
int expectedModCount = modCount;
public boolean hasNext() {
return nextEntry != header;
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
}
//从header.after开始,知道最后一个元素
Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
Entry<K,V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
从源码可以看出来,遍历的时候就跟LinkedHashMap中的数组Entry[]没任何关系了,使用的是header属性进行遍历,这个header就是保存了插入顺序的双向链表,从header.after开始,直到遍历完。
行文至此,就将LinkedHashMap的主要功能的代码介绍完了。再次强调下保持顺序的主要是持有了一个Entry类型的header,这个Entry有before和after两个指针,每次新增元素时,都会在after上进行重新指向,遍历的时候就是通过header开始找after,然后找after的after,以此类推,就做到了有序性,而put和get的时候还是和HashMap一样,都是存放在了Entry[]中。