Java集合源码剖析(三)【TreeMap、LinkedHashmap】

TreeMap的排序是基于对key的排序实现的,它的每一个Entry代表红黑树的一个节点,Entry的数据结构如下:

[java]  view plain copy

  1. static final class Entry<K,V> implements Map.Entry<K,V> {

  2. // 键

  3. K key;

  4. // 值

  5. V value;

  6. // 左孩子

  7. Entry<K,V> left = null;

  8. // 右孩子

  9. Entry<K,V> right = null;

  10. // 父节点

  11. Entry<K,V> parent;

  12. // 当前节点颜色

  13. boolean color = BLACK;

  14. // 构造函数

  15. Entry(K key, V value, Entry<K,V> parent) {

  16. this.key = key;

  17. this.value = value;

  18. this.parent = parent;

  19. }

  20. 。。。。。

  21. }

    构造方法


先来看下TreeMap的构造方法。TreeMap一共有4个构造方法。

1、无参构造方法

[java]  view plain copy

  1. public TreeMap() {

  2. comparator = null;

  3. }

采用无参构造方法,不指定比较器,这时候,排序的实现要依赖key.compareTo()方法,因此key必须实现Comparable接口,并覆写其中的compareTo方法。

2、带有比较器的构造方法

[java]  view plain copy

  1. public TreeMap(Comparator<? super K> comparator) {

  2. this.comparator = comparator;

  3. }

采用带比较器的构造方法,这时候,排序依赖该比较器,key可以不用实现Comparable接口。

3、带Map的构造方法

[java]  view plain copy

  1. public TreeMap(Map<? extends K, ? extends V> m) {

  2. comparator = null;

  3. putAll(m);

  4. }

该构造方法同样不指定比较器,调用putAll方法将Map中的所有元素加入到TreeMap中。putAll的源码如下:

[java]  view plain copy

  1. // 将map中的全部节点添加到TreeMap中

  2. public void putAll(Map<? extends K, ? extends V> map) {

  3. // 获取map的大小

  4. int mapSize = map.size();

  5. // 如果TreeMap的大小是0,且map的大小不是0,且map是已排序的“key-value对”

  6. if (size==0 && mapSize!=0 && map instanceof SortedMap) {

  7. Comparator c = ((SortedMap)map).comparator();

  8. // 如果TreeMap和map的比较器相等;

  9. // 则将map的元素全部拷贝到TreeMap中,然后返回!

  10. if (c == comparator || (c != null && c.equals(comparator))) {

  11. ++modCount;

  12. try {

  13. buildFromSorted(mapSize, map.entrySet().iterator(),

  14. null, null);

  15. } catch (java.io.IOException cannotHappen) {

  16. } catch (ClassNotFoundException cannotHappen) {

  17. }

  18. return;

  19. }

  20. }

  21. // 调用AbstractMap中的putAll();

  22. // AbstractMap中的putAll()又会调用到TreeMap的put()

  23. super.putAll(map);

  24. }

显然,如果Map里的元素是排好序的,就调用buildFromSorted方法来拷贝Map中的元素,这在下一个构造方法中会重点提及,而如果Map中的元素不是排好序的,就调用AbstractMap的putAll(map)方法,该方法源码如下:

[java]  view plain copy

  1. public void putAll(Map<? extends K, ? extends V> m) {

  2. for (Map.Entry<? extends K, ? extends V> e : m.entrySet())

  3. put(e.getKey(), e.getValue());

  4. }

很明显它是将Map中的元素一个个put(插入)到TreeMap中的,主要因为Map中的元素是无序存放的,因此要一个个插入到红黑树中,使其有序存放,并满足红黑树的性质。

4、带有SortedMap的构造方法

[java]  view plain copy

  1. public TreeMap(SortedMap<K, ? extends V> m) {

  2. comparator = m.comparator();

  3. try {

  4. buildFromSorted(m.size(), m.entrySet().iterator(), null, null);

  5. } catch (java.io.IOException cannotHappen) {

  6. } catch (ClassNotFoundException cannotHappen) {

  7. }

  8. }

首先将比较器指定为m的比较器,这取决于生成m时调用构造方法是否传入了指定的构造器,而后调用buildFromSorted方法,将SortedMap中的元素插入到TreeMap中,由于SortedMap中的元素师有序的,实际上它是根据SortedMap创建的TreeMap,将SortedMap中对应的元素添加到TreeMap中。

    插入删除


插入操作即对应TreeMap的put方法,put操作实际上只需按照二叉排序树的插入步骤来操作即可,插入到指定位置后,再做调整,使其保持红黑树的特性。put源码的实现:

[java]  view plain copy

  1. public V put(K key, V value) {

  2. Entry<K,V> t = root;

  3. // 若红黑树为空,则插入根节点

  4. if (t == null) {

  5. // TBD:

  6. // 5045147: (coll) Adding null to an empty TreeSet should

  7. // throw NullPointerException

  8. //

  9. // compare(key, key); // type check

  10. root = new Entry<K,V>(key, value, null);

  11. size = 1;

  12. modCount++;

  13. return null;

  14. }

  15. int cmp;

  16. Entry<K,V> parent;

  17. // split comparator and comparable paths

  18. Comparator<? super K> cpr = comparator;

  19. // 找出(key, value)在二叉排序树中的插入位置。

  20. // 红黑树是以key来进行排序的,所以这里以key来进行查找。

  21. if (cpr != null) {

  22. do {

  23. parent = t;

  24. cmp = cpr.compare(key, t.key);

  25. if (cmp < 0)

  26. t = t.left;

  27. else if (cmp > 0)

  28. t = t.right;

  29. else

  30. return t.setValue(value);

  31. } while (t != null);

  32. }

  33. else {

  34. if (key == null)

  35. throw new NullPointerException();

  36. Comparable<? super K> k = (Comparable<? super K>) key;

  37. do {

  38. parent = t;

  39. cmp = k.compareTo(t.key);

  40. if (cmp < 0)

  41. t = t.left;

  42. else if (cmp > 0)

  43. t = t.right;

  44. else

  45. return t.setValue(value);

  46. } while (t != null);

  47. }

  48. // 为(key-value)新建节点

  49. Entry<K,V> e = new Entry<K,V>(key, value, parent);

  50. if (cmp < 0)

  51. parent.left = e;

  52. else

  53. parent.right = e;

  54. // 插入新的节点后,调用fixAfterInsertion调整红黑树。

  55. fixAfterInsertion(e);

  56. size++;

  57. modCount++;

  58. return null;

  59. }

这里的fixAfterInsertion便是节点插入后对树进行调整的方法,这里不做介绍。

删除操作及对应TreeMap的deleteEntry方法,deleteEntry方法同样也只需按照二叉排序树的操作步骤实现即可,删除指定节点后,再对树进行调整即可。deleteEntry方法的实现源码如下:

[java]  view plain copy

  1. // 删除“红黑树的节点p”

  2. private void deleteEntry(Entry<K,V> p) {

  3. modCount++;

  4. size–;

  5. if (p.left != null && p.right != null) {

  6. Entry<K,V> s = successor §;

  7. p.key = s.key;

  8. p.value = s.value;

  9. p = s;

  10. }

  11. Entry<K,V> replacement = (p.left != null ? p.left : p.right);

  12. if (replacement != null) {

  13. replacement.parent = p.parent;

  14. if (p.parent == null)

  15. root = replacement;

  16. else if (p == p.parent.left)

  17. p.parent.left  = replacement;

  18. else

  19. p.parent.right = replacement;

  20. p.left = p.right = p.parent = null;

  21. if (p.color == BLACK)

  22. fixAfterDeletion(replacement);

  23. } else if (p.parent == null) {

  24. root = null;

  25. } else {

  26. if (p.color == BLACK)

  27. fixAfterDeletion§;

  28. if (p.parent != null) {

  29. if (p == p.parent.left)

  30. p.parent.left = null;

  31. else if (p == p.parent.right)

  32. p.parent.right = null;

  33. p.parent = null;

  34. }

  35. }

  36. }

后面的fixAfterDeletion方法便是节点删除后对树进行调整的方法,这里不做介绍。

其他很多方法这里不再一一介绍。

几点总结

===============================================================

本文对TreeMap的分析较前几篇文章有些浅尝辄止,TreeMap用的没有HashMap那么多,我们有个宏观上的把我和比较即可。

1、TreeMap是根据key进行排序的,它的排序和定位需要依赖比较器或覆写Comparable接口,也因此不需要key覆写hashCode方法和equals方法,就可以排除掉重复的key,而HashMap的key则需要通过覆写hashCode方法和equals方法来确保没有重复的key。

2、TreeMap的查询、插入、删除效率均没有HashMap高,一般只有要对key排序时才使用TreeMap。

3、TreeMap的key不能为null,而HashMap的key可以为null。

注:对TreeSet和HashSet的源码不再进行剖析,二者分别是基于TreeMap和HashMap实现的,只是对应的节点中只有key,而没有value,因此对TreeMap和HashMap比较了解的话,对TreeSet和HashSet的理解就会非常容易。

===

LinkedHashmap源码剖析

==========================================================================

LinkedHashMap简介

===============

LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同。

LinkedHashMap可以用来实现LRU算法(这会在下面的源码中进行分析)。

LinkedHashMap同样是非线程安全的,只在单线程环境下使用。

    LinkedHashMap源码剖析

================================================================================

LinkedHashMap源码如下(加入了详细的注释):

[java]  view plain copy 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. package java.util;

  2. import java.io.*;

  3. public class LinkedHashMap<K,V>

  4. extends HashMap<K,V>

  5. implements Map<K,V>

  6. {

  7. private static final long serialVersionUID = 3801124242820219131L;

  8. //双向循环链表的头结点,整个LinkedHa只哟shMap中只有一个header,

  9. //它将哈希表中所有的Entry贯穿起来,header中不保存key-value对,只保存前后节点的引用

  10. private transient Entry<K,V> header;

  11. //双向链表中元素排序规则的标志位。

  12. //accessOrder为false,表示按插入顺序排序

  13. //accessOrder为true,表示按访问顺序排序

  14. private final boolean accessOrder;

  15. //调用HashMap的构造方法来构造底层的数组

  16. public LinkedHashMap(int initialCapacity, float loadFactor) {

  17. super(initialCapacity, loadFactor);

  18. accessOrder = false;    //链表中的元素默认按照插入顺序排序

  19. }

  20. //加载因子取默认的0.75f

  21. public LinkedHashMap(int initialCapacity) {

  22. super(initialCapacity);

  23. accessOrder = false;

  24. }

  25. //加载因子取默认的0.75f,容量取默认的16

  26. public LinkedHashMap() {

  27. super();

  28. accessOrder = false;

  29. }

  30. //含有子Map的构造方法,同样调用HashMap的对应的构造方法

  31. public LinkedHashMap(Map<? extends K, ? extends V> m) {

  32. super(m);

  33. accessOrder = false;

  34. }

  35. //该构造方法可以指定链表中的元素排序的规则

  36. public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {

  37. super(initialCapacity, loadFactor);

  38. this.accessOrder = accessOrder;

  39. }

  40. //覆写父类的init()方法(HashMap中的init方法为空),

  41. //该方法在父类的构造方法和Clone、readObject中在插入元素前被调用,

  42. //初始化一个空的双向循环链表,头结点中不保存数据,头结点的下一个节点才开始保存数据。

  43. void init() {

  44. header = new Entry<K,V>(-1, null, null, null);

  45. header.before = header.after = header;

  46. }

  47. //覆写HashMap中的transfer方法,它在父类的resize方法中被调用,

  48. //扩容后,将key-value对重新映射到新的newTable中

  49. //覆写该方法的目的是为了提高复制的效率,

  50. //这里充分利用双向循环链表的特点进行迭代,不用对底层的数组进行for循环。

  51. void transfer(HashMap.Entry[] newTable) {

  52. int newCapacity = newTable.length;

  53. for (Entry<K,V> e = header.after; e != header; e = e.after) {

  54. int index = indexFor(e.hash, newCapacity);

  55. e.next = newTable[index];

  56. newTable[index] = e;

  57. }

  58. }

  59. //覆写HashMap中的containsValue方法,

  60. //覆写该方法的目的同样是为了提高查询的效率,

  61. //利用双向循环链表的特点进行查询,少了对数组的外层for循环

  62. public boolean containsValue(Object value) {

  63. // Overridden to take advantage of faster iterator

  64. if (value==null) {

  65. for (Entry e = header.after; e != header; e = e.after)

  66. if (e.value==null)

  67. return true;

  68. } else {

  69. for (Entry e = header.after; e != header; e = e.after)

  70. if (value.equals(e.value))

  71. return true;

  72. }

  73. return false;

  74. }

  75. //覆写HashMap中的get方法,通过getEntry方法获取Entry对象。

  76. //注意这里的recordAccess方法,

  77. //如果链表中元素的排序规则是按照插入的先后顺序排序的话,该方法什么也不做,

  78. //如果链表中元素的排序规则是按照访问的先后顺序排序的话,则将e移到链表的末尾处。

  79. public V get(Object key) {

  80. Entry<K,V> e = (Entry<K,V>)getEntry(key);

  81. if (e == null)

  82. return null;

  83. e.recordAccess(this);

  84. return e.value;

  85. }

  86. //清空HashMap,并将双向链表还原为只有头结点的空链表

  87. public void clear() {

  88. super.clear();

  89. header.before = header.after = header;

  90. }

  91. //Enty的数据结构,多了两个指向前后节点的引用

  92. private static class Entry<K,V> extends HashMap.Entry<K,V> {

  93. // These fields comprise the doubly linked list used for iteration.

  94. Entry<K,V> before, after;

  95. //调用父类的构造方法

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

  97. super(hash, key, value, next);

  98. }

  99. //双向循环链表中,删除当前的Entry

  100. private void remove() {

  101. before.after = after;

  102. after.before = before;

  103. }

  104. //双向循环立链表中,将当前的Entry插入到existingEntry的前面

  105. private void addBefore(Entry<K,V> existingEntry) {

  106. after  = existingEntry;

  107. before = existingEntry.before;

  108. before.after = this;

  109. after.before = this;

  110. }

  111. //覆写HashMap中的recordAccess方法(HashMap中该方法为空),

  112. //当调用父类的put方法,在发现插入的key已经存在时,会调用该方法,

  113. //调用LinkedHashmap覆写的get方法时,也会调用到该方法,

  114. //该方法提供了LRU算法的实现,它将最近使用的Entry放到双向循环链表的尾部,

  115. //accessOrder为true时,get方法会调用recordAccess方法

  116. //put方法在覆盖key-value对时也会调用recordAccess方法

  117. //它们导致Entry最近使用,因此将其移到双向链表的末尾

  118. void recordAccess(HashMap<K,V> m) {

  119. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;

  120. //如果链表中元素按照访问顺序排序,则将当前访问的Entry移到双向循环链表的尾部,

  121. //如果是按照插入的先后顺序排序,则不做任何事情。

  122. if (lm.accessOrder) {

  123. lm.modCount++;

  124. //移除当前访问的Entry

  125. remove();

  126. //将当前访问的Entry插入到链表的尾部

  127. addBefore(lm.header);

  128. }

  129. }

  130. void recordRemoval(HashMap<K,V> m) {

  131. remove();

  132. }

  133. }

  134. //迭代器

  135. private abstract class LinkedHashIterator implements Iterator {

  136. Entry<K,V> nextEntry    = header.after;

  137. Entry<K,V> lastReturned = null;

  138. /**

  139. * The modCount value that the iterator believes that the backing

  140. * List should have.  If this expectation is violated, the iterator

  141. * has detected concurrent modification.

  142. */

  143. int expectedModCount = modCount;

  144. public boolean hasNext() {

  145. return nextEntry != header;

  146. }

  147. public void remove() {

  148. if (lastReturned == null)

  149. throw new IllegalStateException();

  150. if (modCount != expectedModCount)

  151. throw new ConcurrentModificationException();

  152. LinkedHashMap.this.remove(lastReturned.key);

  153. lastReturned = null;

  154. expectedModCount = modCount;

  155. }

  156. //从head的下一个节点开始迭代

  157. Entry<K,V> nextEntry() {

  158. if (modCount != expectedModCount)

  159. throw new ConcurrentModificationException();

  160. if (nextEntry == header)

  161. throw new NoSuchElementException();

  162. Entry<K,V> e = lastReturned = nextEntry;

  163. nextEntry = e.after;

  164. return e;

  165. }

  166. }

  167. //key迭代器

  168. private class KeyIterator extends LinkedHashIterator {

  169. public K next() { return nextEntry().getKey(); }

  170. }

  171. //value迭代器

  172. private class ValueIterator extends LinkedHashIterator {

  173. public V next() { return nextEntry().value; }

  174. }

  175. //Entry迭代器

  176. private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {

  177. public Map.Entry<K,V> next() { return nextEntry(); }

  178. }

  179. // These Overrides alter the behavior of superclass view iterator() methods

  180. Iterator newKeyIterator()   { return new KeyIterator();   }  
    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

小编精心为大家准备了一手资料

以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术

【附】架构书籍

  1. BAT面试的20道高频数据库问题解析
  2. Java面试宝典
  3. Netty实战
  4. 算法

BATJ面试要点及Java架构师进阶资料

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-EJ3fOFBg-1713178349204)]

[外链图片转存中…(img-dcZcw28Z-1713178349204)]

[外链图片转存中…(img-QCsYIP7B-1713178349204)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

小编精心为大家准备了一手资料

[外链图片转存中…(img-tKRg1nmD-1713178349205)]

[外链图片转存中…(img-b40OEpGX-1713178349205)]

以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术

【附】架构书籍

  1. BAT面试的20道高频数据库问题解析
  2. Java面试宝典
  3. Netty实战
  4. 算法

[外链图片转存中…(img-HgEo8wsC-1713178349205)]

BATJ面试要点及Java架构师进阶资料

[外链图片转存中…(img-sdPDkN4j-1713178349205)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值