前言

了解过hashmap和linkedhashmap的应该知道,hashmap不能保证有序,linkedhashmap可以保证插入顺序。但如果想保证key的大小顺序,就得用到treemap。treemap的底层实现是红黑树,不了解红黑树的可以查看我的红黑树学习笔记。

先看成员变量

public class TreeMap<K,V> extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{// 用于接收外部比较器,插入时用于比对元素的大小private final Comparator<? super K> comparator;// 根节点private transient Entry<K,V> root;// 树中元素个数private transient int size = 0;...
}

我们就先关注这3个变量就行:比较器、根节点和元素个数。另外,我们还可以看出TreeMap的继承、实现关系:
1.TreeMap继承AbstractMap,意味着它是一个Map,是一个k-v集合。
2.实现NavigableMap接口,返回有序的key集合
3.实现Cloneable接口,表示能被克隆
4.实现了java.io.Serializable接口,表示能被序列化

数据结构

TreeMap是基于红黑树实现的,树节点Entry实现了Map.Entry,里面包括父节点、左右子节点、k-v、颜色

static final class Entry<K,V> implements Map.Entry<K,V> {K key;V value;Entry<K,V> left;//左子节点Entry<K,V> right;//右子节点Entry<K,V> parent;//父节点boolean color = BLACK;//颜色,红黑...
}

构造器

空构造

默认使用key的自然顺序构建有序树,若key是String类型,就用String类的compareTo方法比较大小;若key为Integer类型,就用Integer的compareTo方法比较。并且,key的类型必须实现Comparable接口,否则无法比较大小。

public TreeMap() {comparator = null;//创建一颗空树
}

带比较器参数的构造

自定义一个类,实现Compartor接口的compare方法。然后传入这个参数用来构造TreeMap

public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;
}

常规操作

put方法

主要就是两个步骤:构建排序二叉树,平衡二叉树
具体步骤:
1、以根节点为初始节点进行检索;
2、与当前节点进行对比,若新增节点值较大则以当前节点的右子节点作为新的当前节点,否则就以当前节点的左子节点作为新的当前节点;
3、循环递归步骤2直到检索出合适的叶子结点
4、将新增节点与步骤3中找到的节点进行对比,若新增节点较大则添加为右子节点,否则为左子节点
do while是核心算法,该算法可以使新增节点找到正确的位置

public V put(K key, V value) {Entry<K,V> t = root;//二叉树的当前节点if (t == null) {//空树,无元素,直接插入compare(key, key); // 比较key,但是真的有意义吗?都是null了比较个毛线?//为k-v键值对创建Entry节点,并作为根节点root = new Entry<>(key, value, null);size = 1;modCount++;//修改次数自增return null;}int cmp;//key排序返回的结果Entry<K,V> parent;//父节点Comparator<? super K> cpr = comparator;//指定的比较器if (cpr != null) {// 外部比较器do {parent = t;//父节点指向上一次循环的tcmp = cpr.compare(key, t.key);//比较新增节点和当前节点的key的大小//新增节点的key < 当前节点的key,则以当前节点的左子节点作为新的当前节点if (cmp < 0)t = t.left;//新增节点的key > 当前节点的key,则以当前节点的右子节点作为新的当前节点    else if (cmp > 0)t = t.right;//返回值等于0,表示两个key值相等,则新值覆盖旧值,并返回新值     elsereturn t.setValue(value);} while (t != null);}else {// cpr为空,则采用默认比较器创建TreeMap集合 if (key == null)//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;elsereturn t.setValue(value);} while (t != null);}//新增节点作为parent子节点Entry<K,V> e = new Entry<>(key, value, parent);if (cmp < 0)  //新增节点的key小于parent的key,做左子树 parent.left = e;else  //右子树parent.right = e;// 插入完成,执行红黑树的性质恢复操作,以维持红黑树的平衡fixAfterInsertion(e);size++;modCount++;return null;
}

####put辅助方法之fixAfterInsertion方法

private void fixAfterInsertion(Entry<K,V> x) {x.color = RED;//新增节点的颜色为红色//循环直到x不是根节点且x的父节点不是红色while (x != null && x != root && x.parent.color == RED) {//x的父节点,位于其祖父节点的左节点if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//获取x的叔叔节点 yEntry<K,V> y = rightOf(parentOf(parentOf(x)));//x的叔叔y是红色if (colorOf(y) == RED) {setColor(parentOf(x), BLACK);//把x的父节点设置成黑色setColor(y, BLACK);//把x的叔叔设置成黑色setColor(parentOf(parentOf(x)), RED);//把x的祖父设置成红色x = parentOf(parentOf(x));//祖父变人了} else {//x的叔叔是黑色//x位于父节点的右子树if (x == rightOf(parentOf(x))) {x = parentOf(x);//x父节点作为xrotateLeft(x);//左旋}setColor(parentOf(x), BLACK);//x父亲变黑setColor(parentOf(parentOf(x)), RED);//x祖父变红rotateRight(parentOf(parentOf(x)));//以x祖父为中心右旋}} else {//x的父节点位于祖父的右子树位置//获取x的叔叔Entry<K,V> y = leftOf(parentOf(parentOf(x)));if (colorOf(y) == RED) {//x的叔叔节点为红色setColor(parentOf(x), BLACK);//x的父亲变黑setColor(y, BLACK);//x的叔叔变黑setColor(parentOf(parentOf(x)), RED);//x祖父变红x = parentOf(parentOf(x));//又变人了} else {//x的叔叔是黑色if (x == leftOf(parentOf(x))) {//x在左子树x = parentOf(x);//x父节点作为xrotateRight(x);//右旋}setColor(parentOf(x), BLACK);//父节点变黑setColor(parentOf(parentOf(x)), RED);//祖父变红rotateLeft(parentOf(parentOf(x)));//左旋}}}root.color = BLACK;//强制根节点变黑,为了保险起见}

put辅助方法之rotateLeft左旋

将新增节点N当做其父节点P,将其父节点P当做新增节点N的左子节点。即:G.left ——> N ,N.left ——> P

private void rotateLeft(Entry<K,V> p) {  if (p != null) {  //获取P的右子节点,相当于新增节点NEntry<K,V> r = p.right;  //将R的左子树设置为P的右子树  p.right = r.left;  //若R的左子树不为空,将P设置为R左子树的父亲  if (r.left != null)  r.left.parent = p;  //将P的父亲设置R的父亲  r.parent = p.parent;  //如果P的父亲为空,则将R设置为根节点  if (p.parent == null)  root = r;  //如果P为其父节点G的左子树,则将R设置为P父节点G左子树  else if (p.parent.left == p)  p.parent.left = r;  //否则R设置为P的父节点G的右子树  else  p.parent.right = r;  //将P设置为R的左子树  r.left = p;  //将R设置为P的父节点  p.parent = r;  }  }

put辅助方法之rotateRight右旋

P.right ——> G、G.parent ——> P

private void rotateRight(Entry<K,V> p) {  if (p != null) {  //将L设置为P的左子树  Entry<K,V> l = p.left;  //将L的右子树设置为P的左子树  p.left = l.right;  //若L的右子树不为空,则将P设置L的右子树的父节点  if (l.right != null)   l.right.parent = p;  //将P的父节点设置为L的父节点  l.parent = p.parent;  //如果P的父节点为空,则将L设置根节点  if (p.parent == null)  root = l;  //若P为其父节点的右子树,则将L设置为P的父节点的右子树  else if (p.parent.right == p)  p.parent.right = l;  //否则将L设置为P的父节点的左子树  else   p.parent.left = l;  //将P设置为L的右子树  l.right = p;  //将L设置为P的父节点  p.parent = l;  }  }

get方法

实际是调用了getEntry方法,简单的二叉查找过程…
1、判断是否指定比较器,指定就调用getEntryUsingComparator;2、校验key;3、遍历,return

public V get(Object key) {Entry<K,V> p = getEntry(key);return (p==null ? null : p.value);//为空就return null,否则就返回p.value
}final Entry<K,V> getEntry(Object key) {// Offload comparator-based version for sake of performanceif (comparator != null)// 如果外部比较器,就采用外部比较器比对查找元素return getEntryUsingComparator(key);if (key == null)// 为空就抛异常,所以Treemap是不允许null的throw new NullPointerException();// 采用key的默认比较器@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;Entry<K,V> p = root;while (p != null) {int cmp = k.compareTo(p.key);//比较keyif (cmp < 0)p = p.left;else if (cmp > 0)p = p.right;elsereturn p;}return null;
}

删除方法

删除操作调用的是deleteEntry方法

private void deleteEntry(Entry<K,V> p) {modCount++;size--;// case1:待删除的节点有两个孩子,找到P的中序后继结点来代替pif (p.left != null && p.right != null) {Entry<K,V> s = successor(p);//寻找P的替代节点p.key = s.key;p.value = s.value;p = s;} // p has 2 children//replacement为替代节点,如果P的左子树存在就用左子树替代,否则就用右子树Entry<K,V> replacement = (p.left != null ? p.left : p.right);// case2:待删除节点只有一个孩子if (replacement != null) {// replacement来替代preplacement.parent = p.parent;if (p.parent == null) //p没有父节点,就直接吧replacement变成根节点root = replacement;else if (p == p.parent.left) //p为左节点,就用replacement替代左节点p.parent.left  = replacement;else //p为右节点,用replacement替代为右节点p.parent.right = replacement;// 把P从树中踢出去p.left = p.right = p.parent = null;// p是黑色,需要调整红黑树平衡if (p.color == BLACK)fixAfterDeletion(replacement);} else if (p.parent == null) { // case3:p没有父节点,表示p是根节点,直接删root = null;} else { //case4:p没有子节点,直接删就行if (p.color == BLACK) //p是黑色,还需要平衡fixAfterDeletion(p);//删除p节点if (p.parent != null) {if (p == p.parent.left)p.parent.left = null;else if (p == p.parent.right)p.parent.right = null;p.parent = null;}}
}

删除辅助方法之successor

寻找替代节点replacement

static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {if (t == null)return null;else if (t.right != null) {// 找右子树中最小元素节点,也就是最左子树Entry<K,V> p = t.right;while (p.left != null)p = p.left;return p;} else {// 找左子树中的最大元素节点,也就是最右子树Entry<K,V> p = t.parent;Entry<K,V> ch = t;while (p != null && ch == p.right) {ch = p;p = p.parent;}return p;}
}

删除辅助方法之fixAfterDeletion

fixAfterDeletion方法用来维持平衡

private void fixAfterDeletion(Entry<K,V> x) {  // 只有当x不是根节点并且是黑色,否则就得一直去迭代while (x != root && colorOf(x) == BLACK) {  if (x == leftOf(parentOf(x))) {//若X节点为左节点  //获取兄弟节点  Entry<K,V> sib = rightOf(parentOf(x));  //如果兄弟节点为红色,变色、左旋if (colorOf(sib) == RED) {       setColor(sib, BLACK);       setColor(parentOf(x), RED);    rotateLeft(parentOf(x));  sib = rightOf(parentOf(x));  }  //若兄弟节点的两个子节点都为黑色,兄弟节点变红 if (colorOf(leftOf(sib))  == BLACK &&  colorOf(rightOf(sib)) == BLACK) {  setColor(sib, RED);  x = parentOf(x);  }   else {  // 如果兄弟节点只有右子树为黑色,兄弟节点和左子树变色、右旋if (colorOf(rightOf(sib)) == BLACK) {  setColor(leftOf(sib), BLACK);  setColor(sib, RED);  rotateRight(sib);  sib = rightOf(parentOf(x));  }  //策略:交换兄弟节点和父节点的颜色,兄弟节点右子树变黑,左旋setColor(sib, colorOf(parentOf(x)));  setColor(parentOf(x), BLACK);  setColor(rightOf(sib), BLACK);  rotateLeft(parentOf(x));  x = root;  }  }   //x为右节点,处理方式跟上面差不多  else {  Entry<K,V> sib = leftOf(parentOf(x));  if (colorOf(sib) == RED) {  setColor(sib, BLACK);  setColor(parentOf(x), RED);  rotateRight(parentOf(x));  sib = leftOf(parentOf(x));  }  if (colorOf(rightOf(sib)) == BLACK &&  colorOf(leftOf(sib)) == BLACK) {  setColor(sib, RED);  x = parentOf(x);  } else {  if (colorOf(leftOf(sib)) == BLACK) {  setColor(rightOf(sib), BLACK);  setColor(sib, RED);  rotateLeft(sib);  sib = leftOf(parentOf(x));  }  setColor(sib, colorOf(parentOf(x)));  setColor(parentOf(x), BLACK);  setColor(leftOf(sib), BLACK);  rotateRight(parentOf(x));  x = root;  }  }  }  setColor(x, BLACK);  }

遍历

顺、逆序遍历之顺序遍历

返回顺序的KEY的集合

Iterator<K> keyIterator() {return new KeyIterator(getFirstEntry());//第一节点
}

KeyIterator方法

final class KeyIterator extends PrivateEntryIterator<K> {KeyIterator(Entry<K,V> first) {super(first);}//执行next就是顺序遍历,不断地拿到下一个元素public K next() {return nextEntry().key;}
}

顺、逆序遍历之逆序遍历

返回逆序的KEY的集合

Iterator<K> descendingKeyIterator() {return new DescendingKeyIterator(getLastEntry());//最后一个结点
}

DescendingKeyIterator方法

final class DescendingKeyIterator extends PrivateEntryIterator<K> {DescendingKeyIterator(Entry<K,V> first) {super(first);}//逆序遍历,获取上一个结点public K next() {return prevEntry().key;}
}

3种遍历方式

1.遍历键值对

Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {Map.Entry entry = (Map.Entry)iter.next();// 获取keykey = entry.getKey();// 获取valuevalue =entry.getValue();
}

2.遍历key

Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {// 获取keykey = (String)iter.next();// 根据key,获取valueinteg = (Integer)map.get(key);
}

3.遍历value

Collection c = map.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {value = iter.next();
}

最后说一句

最后提一下曾经看到过的面试题:为什么不用平衡二叉树为底层实现?
先说一下平衡二叉树的特点:高度平衡,每次修改都要rebalance,这个代价有点大。在插入新节点上,他俩都最多两次旋转;但是删除就不一样了,红黑树最多3次旋转,平衡二叉树需要维护当前节点到root路径上的所有平衡。一个O(1),一个O(log n),维持平衡的动作多了,rebalance的次数就多。所有,红黑树才更适合大量的插入、删除

TreeMap源码分析相关推荐

  1. java集合(6):TreeMap源码分析(jdk1.8)

    前言 TreeMap的基本概念: TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现.该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根 ...

  2. TreeMap源码分析——深入分析(基于JDK1.6)

    TreeMap有Values.EntrySet.KeySet.PrivateEntryIterator.EntryIterator.ValueIterator.KeyIterator.Descendi ...

  3. TreeMap源码分析,看了都说好

    一.简介 TreeMap最早出现在JDK 1.2中,是 Java 集合框架中比较重要一个的实现.TreeMap 底层基于红黑树实现,可保证在log(n)时间复杂度内完成 containsKey.get ...

  4. 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 插入元素 插入元素, ...

  5. 死磕 java集合之TreeMap源码分析(一)——红黑树全解析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  6. 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本 ...

  7. 死磕 java集合之TreeMap源码分析(一)- 内含红黑树分析全过程

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  8. 集合之TreeMap源码分析,简单介绍什么是红黑树,SortedMap和NavigableMap之间的关系和区别

    TreeMap底层实现 1. TreeMap底层实现是红黑树,并且树的节点是内部类Entry类型 2. 红黑树的定义 ① 每个节点是黑色或红色:②根节点是黑色:③所有的叶节点是黑色,不是真正的叶节点, ...

  9. Lucene 源码分析之倒排索引(三)

    上文找到了 collect(-) 方法,其形参就是匹配的文档 Id,根据代码上下文,其中 doc 是由 iterator.nextDoc() 获得的,那 DefaultBulkScorer.itera ...

最新文章

  1. NIS 服务器的配置
  2. android 版本更新原理,Android系统Recovery工作原理之使用update.zip升级过程分析(二)...
  3. 三种工厂模式的分析以及C++实现
  4. 【2012百度之星/初赛上】A:度度熊就是要第一个出场
  5. 机器学习(三十三)——价值函数的近似表示
  6. 【FFMPEG】使用ffmpeg类库打开流媒体
  7. 【Web API系列教程】3.10 — 实战:处理数据(公布App到Azure App Service)
  8. 拓端tecdat|R语言分位数回归Quantile Regression分析租房价格
  9. opencv3.2.0实现视频抽帧,并保存成图片
  10. Openstack Integration with VMware vCenter by Devstack and Opencontrail
  11. Google Earth 使用的经纬度格式及转换
  12. 科研工具:公式书写神器(Mathpix Snipping Tool)
  13. 将本地调试gdb移植到arm板
  14. 导数的零点定理与达布定理
  15. Cannot get a STRING value from a NUMERIC cell
  16. verilog 1bit跨时钟同步器
  17. PMP---项目经理解决冲突的8种模式,走过路过不要错过
  18. 北京华虹正式加入CPChain行业节点
  19. OpenShift 4 - 在单节点 OpenShift 上部署 ODF 存储软件
  20. nrf24l01无线通信模块与51单片机工作原理

热门文章

  1. c++ 包络谱分析代码_基于特征分析谱估计算法(Capon, MUSIC, ESPRIT)的C++实现
  2. 支持多种小程序!阿里云ARMS推出小程序监控
  3. 精确到秒的JQuery日期控件,jquery日历插件,jquery日期插件
  4. office365为新建账号发送欢迎邮件
  5. Node.js 究竟是什么?(zz)
  6. YUV视频格式到RGB32格式转换的速度优化 上篇(转)
  7. CommandBehavior.CloseConnection有何作用
  8. 2019年春季学期第四周作业Compile Summarize
  9. python3知识点之---------字符串的介绍
  10. MapReduce——shuffle