LinkedHashMap解析

前言

LinkedHashMap看名字就知道是链表结构, LinkedHashMap继承了HashMap, 上篇文章已经了解HashMap的数据结构是数组+单链表, 那么LinkedHashMap的数据结构也是如此吗, 让我们通过源码揭开它的面纱.

源码分析

首先看到有两个属性:

     /**
     * The head of the doubly linked list.
     */
    //双向链表的头部
    private transient LinkedHashMapEntry header;
     /**
     * The iteration ordering method for this linked hash map: true
     * for access-order, false for insertion-order.
     */
    //数据元素的排序方法, true:按照访问顺序排序, false:按照插入顺序排序
    private final boolean accessOrder;
  • LinkedHashMapEntry
private static class LinkedHashMapEntry extends HashMapEntry {
        // These fields comprise the doubly linked list used for iteration.
        LinkedHashMapEntry before, after;

        LinkedHashMapEntry(int hash, K key, V value, HashMapEntry next) {
            super(hash, key, value, next);
        }
}

上篇文章分析过HashMapEntry是一个单链表, LinkedHashMapEntry继承了HashmapEntry, 内部又维护了一个before和after, 从而形成了一个双链表.

  • accessOrder
    决定了数据元素的排序方法, 具体实现方法, 后面会介绍到.

构造方法

    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }
    public LinkedHashMap(Map m) {
        super(m);
        accessOrder = false;
    }
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

都是继承了父类HashMap的构造方法, 并初始化了accessOrder.

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

LinkedHashMap重写了父类HashMap构造方法中的init方法, 初始化header.
header是一个key和value都为空, 只保留前后引用的特殊节点.
header.before = header.after = header初始化了一个空的环形链表.

来了解下LinkedHashMapEntry的结构:

private static class LinkedHashMapEntry extends HashMapEntry {
        LinkedHashMapEntry before, after;//前后元素

        LinkedHashMapEntry(int hash, K key, V value, HashMapEntry next) {
            super(hash, key, value, next);
        }

        //修改前后元素的指向, 把该元素从双链表中删除
        private void remove() {
            before.after = after;
            after.before = before;
        }

        //修改该元素及existingEntry的前后指向, 在existingEntry元素之前插入该元素
        private void addBefore(LinkedHashMapEntry existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

        //记录操作
        void recordAccess(HashMap m) {
            LinkedHashMap lm = (LinkedHashMap)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

        void recordRemoval(HashMap m) {
            remove();
        }
    }

这里accessOrder就起作用了, 如果为true(按访问顺序排列), 就把当前位置的元素删除, 加到链表头部前面(实际位置不变, 只是修改双链表中前后元素指向)

重写父类的方法

后面的一些方法都是重写了父类HashMap中的方法, 让我们看下它都重写了哪些方法.

  1. get
public V get(Object key) {
        LinkedHashMapEntry e = (LinkedHashMapEntry)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }

很简单, 就是把父类HashMap中getEntry(key)返回的Entry强转成LinkedHashMapEntry, 最后返回entry的value.
注意, 这里每次获取key对应的值, 都有访问记录e.recordAccess(this), 这里会判断如果accessOrder为true, 就把该元素放到链表头部前面.

  1. createEntry
    LinkedHashMap没有重写父类的put方法, 但是重写了父类中的addEntry和createEntry两个方法来.
void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry old = table[bucketIndex];
        LinkedHashMapEntry e = new LinkedHashMapEntry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

父类HashMap中的createEntry

void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMapEntry e = table[bucketIndex];
        table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
        size++;
    }

经过对比可以发现, LinkedHashMap与HashMap一样, 在数组对应位置上新建了一个Entry, next指向该位置原先的元素, 不同的是LinkedHashMap又把该新建的Entry放到了环形链表头部之前, 因此每次创建的新元素都是放到头部前面, 那头部后面的元素就成了最旧的元素.
网上发现一个很生动形象的图来表达LinkHashMap的数据结构及插入元素的顺序, 这里盗用一下:


LinkedHashMap.png
  1. addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
        //获取链表头部后面的元素, 及最旧的元素
        LinkedHashMapEntry eldest = header.after;
        if (eldest != header) {
            boolean removeEldest;
            size++;
            try {
                //判断是否移除最旧的元素
                removeEldest = removeEldestEntry(eldest);
            } finally {
                size--;
            }
            //为true, 则调用父类HashMap中的removeEntryForKey移除该key对应的元素
            if (removeEldest) {
                removeEntryForKey(eldest.key);
            }
        }
        //然后调用父类的addEntry方法增加元素
        super.addEntry(hash, key, value, bucketIndex);
    }

addEntry方法中LinkedHashMap在HashMap增加元素之前多加了一步是否删除最旧元素的判断.
看过hashMap的源码, 我们知道addEntry方法先判断是否需要扩容然后createEntry, createEntry中LinkedHashMap的改变已经看过, 下面看下扩容时LinkedHashMap做的改变.

  1. transfer
    transfer是数组扩充到原来两倍时, 需要把老数组中所有元素移到新数组的操作.
@Override
    void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;
        for (LinkedHashMapEntry e = header.after; e != header; e = e.after) {
            int index = indexFor(e.hash, newCapacity);
            e.next = newTable[index];
            newTable[index] = e;
        }
    }

对比HashMap的transfer方法.

void transfer(HashMapEntry[] newTable) {
        int newCapacity = newTable.length;
        for (HashMapEntry e : table) {
            while(null != e) {
                HashMapEntry next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

经对比不难发现, HashMap是对老数组中每一个位置的单链表上每一个元素重新计算新坐标,重新放位置, 而LinkedHashMap是对环形链表中的每一个元素重新计算新坐标, 重新放位置, 这样就避免了对每一个元素进行判断, 加快了数组扩容的速率.

  1. containsValue
public boolean containsValue(Object value) {
        // Overridden to take advantage of faster iterator
        if (value==null) {
            for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
                if (e.value==null)
                    return true;
        } else {
            for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
                if (value.equals(e.value))
                    return true;
        }
        return false;
    }

对比HashMap的containsValue:

public boolean containsValue(Object value) {
        if (value == null)
            return containsNullValue();

        HashMapEntry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
        return false;
    }
private boolean containsNullValue() {
        HashMapEntry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
                if (e.value == null)
                    return true;
        return false;
    }

同样, LinkedHashMap是对遍历环形链表的元素, 判断是否包含该value的元素, 而HashMap是遍历数组中每个位置单链表上的每一个元素, 判断是否包含该value的元素.

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

很简单, 就是修改环形链表头部的前后指向.

  1. eldest
public Map.Entry eldest() {
        Entry eldest = header.after;
        return eldest != header ? eldest : null;
    }

获取最旧的元素, 也就是环形链表头部后面的元素. 因为每次创建的新元素都是放到头部之前. 如果accessOrder为true, 查看和修改元素时, 也是把元素重新放到头部之前.

简单总结一下, LinkedHashMap就是在不改变HashMap数据存储结构的基础上又单独维护了一个环形链表. 每次创建元素的时候放到环形链表头部之前, 如果按照访问顺序排列的话, 每次查看或者修改元素也都重新放到头部之前, 这样很容易就能获取到最老的元素.

你可能感兴趣的:(LinkedHashMap解析)