LinkedHashMap和LinkedHashSet源码设计总结

HashSet是基于HashMap做了个皮包来实现的

LinkedHashSet也是基于LinkedHashMap做的一个皮包

LinkedHashMap

 

 

 

是HashMap的子类,在HashMap的基础上,在Entry之间使用双向链表加持

下面是核心属性和方法

public class MyLinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {

    static class Entry<K,V> extends HashMap.Node<K,V>{
        //final int hash;         -----以下四个注释的属性是HashMap.Node的属性,这里方便理解
        //final K key;
        //V value;
        //HashMap.Node<K,V> next;
        Entry<K,V> before;     ----保存当前的Entry的前后节点
        Entry<K,V> after;
    }

    /**
     * The head (eldest) of the doubly linked list
     * 双向链表的头
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.
     * 双向链表的尾
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * 这三个方法是HashMap的方法,是空实现
     * 在LinkedHashMap都重写了,分别是查,插入,删除元素时如何维护双向链表
     */
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }
}

LinkedHashMap的put方法,调用的就是HashMap的put方法,然后在最后调用子类(就是LinkedHashMap)覆盖了的afterNodeXxx方法,处理双向链表的逻辑

插入后是如何维持链表顺序的

// link at the end of list 将链表的尾节点和当前节点串起来
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    // 将当前tail值记做last,因为下面要对 tail 赋值
    LinkedHashMap.Entry<K,V> last = tail;
    //将新创建的节点p作为尾结点tail的新值
    tail = p;
    // last为null,就是当前还没有节点
    if (last == null)
        // 插入的这个节点是头也是尾 
        head = p;
    else {
         // 否则将p和last的双向关系建立起来
        p.before = last;
        last.after = p;
    }
}

accessOrder属性

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

默认为false,表示按插入顺序遍历元素

true表示按访问顺序遍历,此时任何一次的操作,包括put、get操作,都会改变map中已有的存储顺序

关于这个属性的一个例子

public void test() {
     // 设置为true,按照访问顺序排序
     HashMap<Integer, Integer> m = new LinkedHashMap<>(10, 0.75f, true);
     // 每次调用 put(),数据将被添加到链表的尾部
     m.put(1, 11);
     m.put(2, 22);
     m.put(3, 33);
     m.put(4, 44);
     // 再次插入key = 3,先查找这个key 3是否已存在,发现存在,将 (3,33) 删除,将新的 (3,333) 放到链表尾部
     m.put(3, 333);
     // 访问 key = 2,会将2-22这个Node移动到链表尾部
     m.get(2);
     // 此时结果应该是1 4 3 2
     for (Map.Entry e : m.entrySet()) {
         System.out.println(e.getKey());
     }
 }

LinkedHashMap的一个典型应用:LRU缓存(淘汰掉访问最少的,最老的元素)

public class LRU<K,V> extends LinkedHashMap<K,V> implements Map<K,V> {
    private int maxCapacity;
    public LRU(int capacity){      
        //第三个参数accessOrder传true
        super(16,0.75F,true);
        this.maxCapacity=capacity;
    }
    //覆盖的方法,这个会决定当超过链表最大容量时是否移除最老最少光顾的元素
    protected boolean removeEldestEntry(Entry entry){
        return (size()>this.capacity);
    }
}

LinkedHashSet

 

 

 

关于顺序

插入顺序指的是首次插入时的顺序,重复插入的元素会被重新插入,但是不会改变这个元素首次插入的顺序,比如插入1,2,3,1,结果是1,2,3

Note that insertion order is <i>not</i> affected if an element is <i>re-inserted</i> into the set

关于时间复杂度

和HashSet一样,如果hash方法可以使得元素能够非常散列的落在buckets中,add,contains,remove方法的时间复杂度是O(1)

Like HashSet, it provides constant-time performance for the basic operations (add,contains and remove), assuming the hash function disperses elements properly among the buckets

继承了HashSet,底层存储使用的是LinkedHashMap,自己就5个方法,4个是构造方法,构造方法调用的是这个方法

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

非线程安全

使用如下方式创建一个线程安全的LinkedHashSet

Set s = Collections.synchronizedSet(new LinkedHashSet(...));

 

posted @ 2022-08-05 18:56  鼠标的博客  阅读(44)  评论(0编辑  收藏  举报