集合框架源码分析五之LinkedHashMap,LinkedHashSet

LinkedHashMap是为了解决遍历Hash表的无序问题,它内部维护了一个链表用于记录你插入元素(或你访问元素的顺序)的位置,遍历时直接遍历链表,元素的顺序即为你插入的顺序,但是Entry对象要多加两个成员变量before和after用于记录链表的前驱和后继。所以LinkedHashMap的的存储效率要低于HashMap,但是遍历效率要高于HashMap。 



import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;


/**
 * LinkedHashMap内部不仅存储了一个散列表,
 * 也维护了一个链表,
 * 根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。
 * 默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素
 * 移至链表尾部,不断访问可以形成按访问顺序排序的链表。
 * 可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素
 */
public class LinkedHashMap<K,V> extends HashMap<K,V>
    implements Map<K,V>
{


    private static final long serialVersionUID = 3801124242820219131L;


    /**
     * 此Map中维护的一个双向循环链表的头节点
     */
    private transient Entry<K,V> header;


    /**
     * 
     * 遍历Map的顺序的变量,
     * true为按访问顺序遍历
     * false为按插入顺序遍历
     */
    private final boolean accessOrder;


    /**
     * 
     * 构造一个空的,按插入顺序遍历的LinkedHashMap实例,
     * 初始容量与加载因子由自己指定
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }


    /**
     * 构造一个空的,按插入顺序遍历的LinkedHashMap实例,
     * 初始容量由自己指定
     */
    public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
        accessOrder = false;
    }


    /**
     * 
     * 构造一个空的,按插入顺序遍历的LinkedHashMap实例
     * 初始容量与加载因子按HashMap的默认值16与0.75
     */
    public LinkedHashMap() {
    super();
        accessOrder = false;
    }


    /**
     * 构造一个非空,按插入顺序遍历的LinkedHashMap实例
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);
        accessOrder = false;
    }


    public LinkedHashMap(int initialCapacity,
float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
    
    /**
     * 
     * 在HashMap每个构造方法中都会调用一次此init()方法
     * 这里是初始化一个双向循环链表的头节点
     */
    void init() {
        header = new Entry<K,V>(-1, null, null, null);
        header.before = header.after = header;
    }


    /**
     * 
     * 在父类HashMap进行容量扩充时会调用此方法
     * 把原来Entry数组中的对象经过重新计算索引转移到此newTable中
     * 这里与HashMap中的transfer方法的实现不再相同,而且比它更快。
     * 
     * HashMap是遍历Entry数组,需要检查是否为null,如果不为空的话则就需
     * 要遍历数组中的链表,而这里所有的元素都链接成了一个链表,直接遍历此
     * 链表就可以遍历出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); //根据新容量重新计算index
            e.next = newTable[index]; //最后添加的节点放最前面
            newTable[index] = e;
        }
    }




    /**
     * 
     *  查找Map中是否含有本值,这里重写了HashMap的containsValue方法
     *  因为在LinkedHashMap中只需要遍历链表查找,这比HashMap遍历table查找更加
     *  高效和快速
     */
    public boolean containsValue(Object value) {
        // Overridden to take advantage of faster iterator
        if (value==null) {
            for (Entry e = header.after; e != header; e = e.after)
                if (e.value==null)
                    return true;
        } else {
            for (Entry e = header.after; e != header; e = e.after)
                if (value.equals(e.value))
                    return true;
        }
        return false;
    }


    /**
     * 
     * 通过key值获得value,如果没有找到此key则返回null。
* 不过返回null也可能是其value为null
* 通过contain方法可判断Map中是否含有此键

* 重写此方法的原因是:
* 如果map中链表是按照访问顺序进行排序,
* 就需要把本次访问的元素移到链表尾部,表示
* 这是你最新访问的元素,以后可能会继续访问它

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


    /**
     * 移除元素,清空链表
     */
    public void clear() {
        super.clear();
        header.before = header.after = header;
    }


    /**
     * LinkedHashMap 的Entry对象.
     */
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // 包含其前驱和后继的信息.
        Entry<K,V> before, after;


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


        /**
         * 
         * 在链表中移除此对象只需要修改前驱和后继的指针
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }


        /**
         * 
         * 把元素插入到指定的存在的元素之前
         * 链表的插入也是修改指针
         */
        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }


        /**
         * 
         * 当调用父类的put或putAll方法,发现要插入的键已经存在时会调用此方法,
         * 当调用LinkedHashMap的get方法时会调用此方法。
         * 如果LinkedHashMap是按访问顺序遍历的,就移动此Entry
         * 到链表的最后位置,否则do nothing
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove(); //移除它
                addBefore(lm.header); //再把她添加到链表尾部
            }
        }


        /**
         * 在移除元素时会调用此方法,即调用
         * 父类的remove(key)方法时调用它
         * 这里是改变链表指针,移除链表中的此元素
         */
        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }


    private abstract class LinkedHashIterator<T> implements Iterator<T> {
    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;
}


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


    //依次重写next方法,返回不同的迭代器。
    private class KeyIterator extends LinkedHashIterator<K> {
    public K next() { return nextEntry().getKey(); }
    }


    private class ValueIterator extends LinkedHashIterator<V> {
    public V next() { return nextEntry().value; }
    }


    private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
    public Map.Entry<K,V> next() { return nextEntry(); }
    }


    // These Overrides alter the behavior of superclass view iterator() methods
    Iterator<K> newKeyIterator()   { return new KeyIterator();   }
    Iterator<V> newValueIterator() { return new ValueIterator(); }
    Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }


    /**
     * 
     * 重写父类addEntry方法
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);


        
        /*如果子类重写了removeEldestEntry方法并返回true,
                          将在map中移除链表中的第一个元素,否则检测是否需要扩充容量。
          eldest元素即为链表中的第一个元素*/
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length); //扩充至原来的2倍
        }
    }


    /**
     * 
     * 重写父类的createEntry方法,此方法不需要检测是否要扩充容量,
     * 与HashMap中此方法的唯一区别时,这里不仅把元素插入到散列表中,
     * 而且将元素插入到了链表尾
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }


    /**
     * 
     * 此方法会在调用put()或putAll()方法时调用,此方法告诉linkedHashMap
     * 是否在添加一个元素后移除最老的元素
     * 比如下面代码会让LinedHashMap的大小始终保持在100
     *     private static final int MAX_ENTRIES = 100;
     *
     *     protected boolean removeEldestEntry(Map.Entry eldest) {
     *        return size() > MAX_ENTRIES;
     *     }
     *  此方法默认返回false,需要子类去复写此方法
     *  
     *  什么是eldest元素?
     *  如果按插入问顺序来说,是map中最先插入的元素
     *  如果按访问顺序来说,是map中最久未被访问的元素
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
}
关于LinkedHashSet 

/**
 * LinkedHashSet实际上是基于LinkedHashMap的基础上实现的,
 * LinkedHashSet继承自HashSet,在HashSet中有一构造方法
 * HashSet(int initialCapacity, float loadFactor, boolean dummy) 
 * 第三个参数dummy为true时new出了一个LinkedHashMap实例,以Set中的
 * 元素为键,以一个Object的虚假的对象为值,所以HashSet中的元素不可能重复。
 * 以下构造函数dummy都为true
 */
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {


private static final long serialVersionUID = -2851667679971038690L;




public LinkedHashSet(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor, true);
}




public LinkedHashSet(int initialCapacity) {
    super(initialCapacity, .75f, true);
}




public LinkedHashSet() {
    super(16, .75f, true);
}




public LinkedHashSet(Collection<? extends E> c) {
    super(Math.max(2*c.size(), 11), .75f, true);
    addAll(c);
}
}



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值