LinkedHashMap源码解析(基于JDK1.7)

LinkedHashMap是HashMap的子类,在拥有HashMap功能之外可以保存元素插入顺序,使得元素遍历顺序与元素插入顺序相同。同时LinkedHashMap还可以根据元素访问的时间先后顺序来遍历元素,Lru(Least recently used)算法中就是应用了LinkdHashMap这一性质。那么这些功能具体是如何实现的,只能通过解析源码来揭秘了。

LinkedHashMap构造函数

构造函数除了正常调用父类HashMap的构造函数之外,还初始化了accessOrder为false.accessOrder为false时,表明遍历元素是按照插入顺序来访问,即遍历输出顺序和当初元素插入顺序相同;accessOrder为true时,表明遍历元素顺序是根据访问元素时间先后顺序来遍历元素,即最近访问的元素最后输出。具体会在下文解释。

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

在HashMap构造函数有个init函数没有实现,在LinkedHashMap中实现了。可以看出这个函数new了一个LinkedHashMapEntry类型的header节点,LinkedHashMapEntry继承了HashMapEntry,增加了before和after指针。这个before和after和next有什么区别?从第二行代码只能看出是建立了一个环形双向链表。要想知道有什么区别,需要继续挖掘。

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

LinkedHashMap的put和扩容

LinkedHashMap没有实现put函数,还是调用HashMap的put函数。

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        int i = indexFor(hash, table.length);
        for (HashMapEntry e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

其中recordAccess是HashMapEntry的成员函数,在HashMap中是个空函数,具体实现在LInkedHashMap中。可以猜想这个函数与LInkedHashMap的顺序存储有关。


        void recordAccess(HashMap m) {
            LinkedHashMap lm = (LinkedHashMap)m;
            //首先判断是否为当前是否为访问顺序模式。
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
      // 这里其实是删除双链表单节点的操作,
        private void remove() {
            //当前节点的前一个节点的after指针指向当前节点的后一个节点。
            before.after = after;
           // before指针指向当前节点的前一个节点。
            after.before = before;
        }
     // 将当前节点插入到环形链表的头节点的前一个节点,其实也就是双链表的尾部
        private void addBefore(LinkedHashMapEntry existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

因此在put操作时,如果该key的元素存在,recordAccess会通过删除节点并将该节点重新插入到双链表的尾部。这样就可以解释第一节中的疑问,在遍历时最近访问的元素会最后输出,就是因为每次put操作会把节点重新放置到双链表尾部。

LinkedHashMap重写了addEntry,但是removeEldestEntry默认返回false。其实和HashMap的addEntry一样。同时重写了createEntry().相比于HashMap,不同在于多调用了addBefore函数。在上面已经分析了,是将该节点移动到双链表的尾部。也就是LinkedHashMap除了要维护一个数组加链表,还要维护一个双链表。

    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--;
            }
            if (removeEldest) {
                removeEntryForKey(eldest.key);
            }
        }
        super.addEntry(hash, key, value, bucketIndex);
    }
    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++;
    }

LinkedHashMap的扩容也是不同的,重写了resize中的transfer函数。HashMap中是通过遍历每个链表来将旧元素拷贝到新的数组中,而LinkedHashMap由于有双向链表来维护所有元素,因此直接遍历双向链表来拷贝旧元素。从遍历的效率来说,遍历双向链表的效率要高于遍历table,因为遍历双向链表是N次(N为元素个数);而遍历table是N+table的空余个数(N为元素个数)。

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

你可能感兴趣的:(LinkedHashMap源码解析(基于JDK1.7))