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(...));