您的位置:首页 > 编程语言 > Java开发

【源码解析】JDK源码之LinkedHashMap

2017-04-11 08:21 441 查看
LinkedHashMap源码,基于jdk1.6.43

 

他继承了HashMap,并且实现了插入和访问的有序功能

public class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V> 
其也有一个Entry内部类,继承了HashMap的Entry内部类,但是增加了两个属性before、after。源码对这两个属性的注释含义是这两个域包含两个链表用于迭代。

private static class Entry<K, V> extends HashMap.Entry<K, V>
// These fields comprise the doubly linked list used for iteration.
Entry<K, V> before, after;

代码中的一个属性header,是双向链表的头指针。由此我们推断,LinkedHashMap的内部实现是在HashMap的基础上增加了一个额外的双向链表,用于保存数据的插入顺序。

private transient Entry<K, V> header;

布尔类型变量,如果为true的话则按访问顺序排序,如果为false的话则按插入顺序排序。

private final boolean accessOrder;

下面的几个构造方法都直接调用了父类的构造方法,只是调用的参数各不相同,但是共同点是都将accessOrder设置为false。

public LinkedHashMap(int initialCapacity, float loadFactor)

public LinkedHashMap(int initialCapacity)

public LinkedHashMap()

public LinkedHashMap(Map<? extends K, ? extends V> m)

下面的构造方法有所不同,可以传入accessOrder,即可以在构造时改变accessOrder的值。

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
 
下面的init方法覆盖了父类的init方法,我们分析HashMap源码时了解到在构造的最后会调用init方法,默认的init方法实现为空,其目的是交由子类进行各自的处理,其在LinkedHashMap中发挥了重要的作用。下面的代码含义就是在构造完成后,初始化头指针,并根据双向链表的特性将头指针的前后指针都指向自己本身,实现双向链接的初始化操作。

void init() {
header = new Entry<K, V>(-1, null, null, null);
header.before = header.after = header;
}


下面是上面的init方法调用的本身Entry内部类的构造器,传入的分别是hash值、key值、value值和HashMap中需要指向数组后继元素的next指针变量。我们看到头指针初始化的hash值为-1作为标志位。

Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {
super(hash, key, value, next);
}


重写了父类的transfer方法,该方法的目的是在Map的数组扩容时,重新指定每一个元素的位置,但是其实现与HashMap不同,HashMap的实现是遍历整个数组,对于每个数组元素的单链表重新计算位置,但是LinkedHashMap并没有直接采用父类的方法,而是遍历了每个元素的前后指针,我们知道链表的遍历速度比数组快一些,所以这种实现是一个很高效率的实现。

void transfer(HashMap.Entry[] newTable) {
int newCapacity = newTable.length;
for (Entry<K, V> e = header.after; e != header; e = e.after) {
int index = indexFor(e.hash, newCapacity);
e.next = newTable[index];
newTable[index] = e;
}
}


下面的方法也覆盖了父类的方法重新实现,目的也是为了更快的实现遍历。

public boolean containsValue(Object value)

 

调用父类的clear方法,并将头指针的前后指针重置为header。

public void clear() {
super.clear();
header.before = header.after = header;
}


下面的方法使用protected修饰,明显就是为了让子类进行后续的修改,且目前的实现中一直返回false。

protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return false;
}


下面的方法比较重要,LinkedHashMap中没有覆盖父类的put方法,但是他是如何来维护链表的顺序呢?关键就是下面的addEntry方法,这个addEntry根据HashMap中的put函数中的代码可知他会在put方法的最后被调用。HashMap的addEntry方法中直接放置新加入元素的位置,然后决定是否扩充数组的大小。但是LinkedHashMap的addEntry方法中,在判断是否扩充数组前调用了createEntry方法,来实现维护链表顺序。这个方法在createEntry之后进行了一步判断removeEldestEntry,从上面的代码可以看到这个方法目前是false,就会走else的分支。if这个分支中的操作其实会删除最先插入的元素。但是实际在LinkedHashMap中并没有使用。

void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K, V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}


下面就是createEntry方法

void createEntry(int hash, K key, V value, int bucketIndex) {
// 根据元素应该放置的位置获得数组该位置的元素
HashMap.Entry<K, V> old = table[bucketIndex];
// 根据获得的元素创建一个Entry对象
Entry<K, V> e = new Entry<K, V>(hash, key, value, old);
// 将新创建的对象赋值替代原有的数组元素
table[bucketIndex] = e;
// 将这个元素插入到头指针之前
e.addBefore(header);
// map的大小加一
size++;
}


这个get方法覆盖了父类的get方法,其目的是为了调用e.recordAccess(this);这一行代码。

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;
}


上面那一行代码的意义如下,根据accessOrder的设置来修改双向链表的顺序,以实现根据访问来排序双向链表。

void recordAccess(HashMap<K, V> m) {
LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}


还有一个remove方法定义在Entry内部,用于删除元素,LinkedHashMap中没有remove方法,所以remove方法的调用其实是调用父类的方法,但是HashMap的remove方法在结尾调用了Entry的recordRemoval方法,而LinkedHashMap在重写recordRemoval方法时调用了其Entry的remove方法,最终实现了在删除元素时对自身链表的操作,从链表中删除元素并改变指针的指向。

private void remove() {
before.after = after;
after.before = before;
}

void recordRemoval(HashMap<K, V> m) {
remove();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: