注意 本系列文章是笔者在参考了大量优秀文章的基础上的理解,由于水平有限,难免会出现错误,望批评指正,同时我也会把自己最新的理解及时更新到文章中去。最后,我会在文中注明参考文章,特别感谢这些无私奉献的作者。
Java Map 类型集合提供了两种有序的Map,即:LinkedHashMap 和 TreeMap。
1 LinkedHashMap LinkedHashMap 继承自 HashMap,并在 HashMap 的基础上添加了有序的功能。
LinkedHashMap 通过 accessOrder 关键属性控制了不同的有序逻辑。
1、accessOrder 为 false 时,按照插入顺序排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put("key1", "value1"); linkedHashMap.put("key2", "value2"); linkedHashMap.put("key3", "value3"); linkedHashMap.put("key4", "value4"); linkedHashMap.put("key5", "value5"); Iterator l = linkedHashMap.entrySet().iterator(); while (l.hasNext()) { System.out.println(l.next()); } 输出结果: key1=value1 key2=value2 key3=value3 key4=value4 key5=value5
2、accessOrder 为 true 时,按照访问顺序排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>(16,0.75f,true); linkedHashMap.put("key1", "value1"); linkedHashMap.put("key2", "value2"); linkedHashMap.put("key3", "value3"); linkedHashMap.put("key4", "value4"); linkedHashMap.put("key5", "value5"); linkedHashMap.get("key1"); Iterator l = linkedHashMap.entrySet().iterator(); while (l.hasNext()) { System.out.println(l.next()); } 输出结果: key2=value2 key3=value3 key4=value4 key5=value5 key1=value1
1.1 put(K key, V value) 添加元素时,LinkedHashMap 保证了添加元素的有序性,依旧调用了 HashMap 中的 putVal 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
再来看,其中的 newNode 方法实际上是 HashMap提供的一个模版方法,子类 LinkedHashMap 重写了该方法,我们再来看下 newNode 在子类的定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } // link at the end of list. private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }
子类的 newNode 方法除了完全实现了和父类中 newNode 方法一样的功能外,还额外实现了 linkNodeLast 方法,该方法按照添加顺序将 Entry 添加到了 LinkedHashMap 自身维护链表的尾部。LinkedHashMap 的功能大部分都是按照这种子类复写的方式实现的,这里不再一一列举了。
下面,我们来利用 LinkedHashMap 实现一个 LRU 算法,这也是 LinkedHashMap 被经常运用的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static class LRU<K, V> extends LinkedHashMap<K, V> { int maxSize; public LRU(int initialCapacity, float loadFactor, boolean accessOrder, int maxSize) { super(16, 0.75f, true); this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > maxSize; //当元素个数大于 maxSize 时,移除最少被访问的元素。 } }
这里重写了 removeEldestEntry 方法,该方法在 LinkedHashMap 中默认返回 false 。
1 2 3 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
LinkedHashMap 在每次 put 完元素后都会依据该方法判断是否移除链表中第1个元素。
1 2 3 4 5 6 7 8 //afterNodeInsertion 正是父类 HashMap 的 put 方法最后调用的逻辑。 void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); //移除链表中的首个元素。 } }
好了,上面就是对 LinkedHashmap 的介绍了,原理很简单,其实就是在 HashMap 的基础添加有序了功能而已。本篇是JDK 1.8 的代码讲解的,JDK 1.7 的思路一致,这里就不再赘述了。
2 TreeMap TreeMap 的源码笔者就不再逐个分析了。对于它的用法,认真看完下面链接中的文章就可以了。
点击跳转>>
上面链接文章中提到了 TreeMap 的 key 不需要重写 equal 和 hash 方法,那么他是如何将元素对号入座的呢?
下面是 TreeMap 的 put 方法实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); //红黑树相关操作 size++; modCount++; return null; }
由上面代码可知,TreeMap 维护的是一颗红黑树,Comparable|Comparator 决定了元素是添加到左子树还是右子树。即:cmp < 0 添加到左子树,cmp > 0 添加到右子树,cmp = 0 则替换,所以TreeMap 的 key 不需要重写 equal 和 hash 方法来确定 key 的位置。同样,TreeMap 的 get 方法也是类似的查找策略,这里不再赘述。
参考资料 亮哥教你学Java