Java容器类浅析三-----保证插入顺序的HashMap--LinkedHashMap的存取原理

上一节我们说到HashMap的存取原理,并一步步地分析了其主要的成员变量、构造函数以及体现其算法的put,get方法。本文将继续分析HashMap的子类—LindedHashMap,来解决上一节提到的,如何保证迭代顺序和插入顺序的一致性的问题。

一、概述

public class LinkedHashMap extends HashMap implements Map

LinkedHashMap直接继承于HashMap,且其内部属性和存取算法与HashMap基本一致。
那么为什么LinkedHashMap可以做到保证迭代顺序和插入顺序一致呢? 这就要归功于LinkedHashMap中的一个数据结构—Entry header了,header是一个双向链表中的首节点,该双向链表保存了entry插入的顺序,在迭代时,实际上是在迭代该双向链表。每次调用put()方法时,第一步和HashMap的put()方法一致,第二步就是将该元素的before节点指向header的before节点,after节点指向header,将header的before节点指向该元素,以完成双向链表的记录顺序。

二、成员变量

/**
 * The head of the doubly linked list.
 */
private transient Entry header;

双向链表的头结点。

/**
 * The iteration ordering method for this linked hash map: true
 * for access-order, false for insertion-order.
 *
 * @serial
 */
private final boolean accessOrder;

标识迭代顺序的标志,true表示按照访问顺序进行迭代(),false表示按照插入的顺序进行迭代。

三、构造函数

public LinkedHashMap() {
    super();
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(Map m) {
    super(m);
    accessOrder = false;
}

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

构造函数和HashMap的基本一致,只是多了将accessOrder设为false的操作;而且可以将一个HashMap的对象强转为LinkedHashMap的对象。
而且,LinkedHashMap重写了父类的钩子函数–init(),将双向链表的头结点初始化,并将其before、after节点指向了自己,其实就是对双向链表进行初始化。

四、put()方法

打开LinkedHashMap的源码,Ctrl+O,输入put,你会惊奇的发现这个类里面并没有put方法,说明LinkedHashMap没有重写父类的put()方法,只是继承了父类的put()方法。那么问题来了,如果没有重写put()方法,LinkedHashMap是如何在插入数据时将数据记录到双向链表中的呢?

答案是:LinkedHashMap重写了父类的addEntry()和createEntry()方法,并在createEntry()方法中将元素插入到双向链表的尾部。

void addEntry(int hash, K key, V value, int bucketIndex) { 
    //调用父类的addEntry方法
    super.addEntry(hash, key, value, bucketIndex);

    //实现移除最近最少使用的元素,不过从源码上看,该方法返回的值一直为false
    // Remove eldest entry if instructed
    Entry eldest = header.after;
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    }
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    HashMap.Entry old = table[bucketIndex];
    Entry e = new Entry<>(hash, key, value, old);
    table[bucketIndex] = e;
    e.addBefore(header);//将刚加入桶中的元素添加到记录插入顺序的双向链表中的尾节点。
    size++;
}

//双向链表尾插法
private void addBefore(Entry existingEntry) {
    after  = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}

五、get()方法

LikedHashMap重写了父类的get()方法,第一步调用父类的getEntry()方法,获取到相应的entry元素,第二步调用重写的recordAccess()方法,来记录访问顺序;

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

只有当accessOrder为true时,即指定迭代顺序为访问顺序时,才会将当前元素插入到双向链表的头结点。

六、总结

LinkedHashMap作为HashMap的子类,其实现方式十分相似,区别在于,LinkedHashMap可以保证迭代顺序和插入顺序的一致,当你发现从数据库里读取的数据和返回的数据顺序不一致时 就要考虑使用LinkedHashMap了。

你可能感兴趣的:(源码分析)