1.TreehMap的内部结构
2.TreehMap构造函数
3.元素新增策略
4.元素删除
5.元素修改和查找
6.特殊操做
7.扩容
8.总结html
首先确认一点,treemap是一个基于红黑树的map,这个集合的一个特色就是排序,是的若是不是排序,那么hashmap能够完美取代java
再开始前咱们要熟悉一个红黑树的概念:node
对于红黑树的定义:函数
1.节点是红色或黑色。
2.根是黑色。
3.全部叶子都是黑色(叶子是NIL节点)。
4.每一个红色节点必须有两个黑色的子节点。(从每一个叶子到根的全部路径上不能有两个连续的红色节点。)
5.从任一节点到其每一个叶子的全部简单路径都包含相同数目的黑色节点。ui
经过已排序的map进行新的treemap的构造生成,这里用到了一个buildFromSorted函数,这个方法会递归数据this
参考buildFromSorted实现,这个实现的红黑树,说白了,就是把最后一层变成红色,以上全做为黑色spa
Put操做.net
这个操做没啥,就是遍历这颗树,左边小,右边大,遍历到合适的位置设置值,或者建立新的节点插入,并默认设置为黑色
重点在于后面的变更以后,若是进行红黑树的修复
针对红黑树的变更,能够参考以上总结的规则:https://www.cnblogs.com/cutter-point/p/10976416.html3d
针对于红黑树的操做主要就是左旋和右旋的操做指针
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { //若是根节点为空,而后这个比较实际上是起一个类型检查做用 compare(key, key); //建立root节点 root = new Entry<>(key, value, null); size = 1; modCount++; return null; } //若是根节点存在 int cmp; Entry<K,V> parent; //判断是否设置了比较器 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(); //就提早key的比较器 @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); } //若是一直到t为空还没找到,那么就建立新值 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; } //获取对应节点的父节点 private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) { return (p == null ? null: p.parent); } //求当前节点的左右节点 private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) { return (p == null) ? null: p.left; } private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) { return (p == null) ? null: p.right; } //获取当前节点的颜色,为空即为黑 private static <K,V> boolean colorOf(Entry<K,V> p) { return (p == null ? BLACK : p.color); } private static <K,V> void setColor(Entry<K,V> p, boolean c) { if (p != null) p.color = c; } 红黑树的修复操做,主要重点就是对从一个节点到叶子节点的黑色节点个数相同为基准 //进行红黑树修复 private void fixAfterInsertion(Entry<K,V> x) { x.color = RED; //新增的节点默认是红色,而后判断是进行左旋,右旋,仍是其余操做 //进行旋转操做的前提是对应节点的父节点是红色 while (x != null && x != root && x.parent.color == RED) { //判断父节点 是不是祖父的左节点 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //获取祖父节点的右节点 Entry<K,V> y = rightOf(parentOf(parentOf(x))); //判断另一个节点是红是黑 if (colorOf(y) == RED) { //若是祖父的兄弟节点是红色,那么主要是吧兄弟节点改为黑色便可,这样祖父的兄弟节点至关于增长了一个黑色个数 //若是是红,那么就直接修改颜色便可 setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { //若是是黑色,说明两边的分支走到叶子节点的不是相同数目的黑色节点 //若是x是右节点,那么就左旋,若是是左节点就右旋 if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { Entry<K,V> y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } //最后根节点必定是黑 root.color = BLACK; } 左旋右旋操做就是把以对应的节点为核心进行节点的上升和降低,而后要复合红黑树的规范 private void rotateLeft(Entry<K,V> p) { //左旋操做 if (p != null) { //若是节点不为空,进行左旋的时候,获取节点的右节点 Entry<K,V> r = p.right; p.right = r.left; if (r.left != null) r.left.parent = p; r.parent = p.parent; if (p.parent == null) root = r; else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r; r.left = p; p.parent = r; } } /** From CLR */ private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left; p.left = l.right; if (l.right != null) l.right.parent = p; l.parent = p.parent; if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l; l.right = p; p.parent = l; } }
进行元素删除的思想是:
若是我须要删除这个节点,那么首选判断右子树是否存在,若是存在那么就找到右边子树的最小值,也就是最小的叶子节点,用一个最靠近的节点的数据替换须要删除的节点的数据,这样就保障二叉索引树的特性,而后根据变更的节点颜色从新修复这颗红黑树的颜色
//节点删除操做 public V remove(Object key) { //获取到这个节点 Entry<K,V> p = getEntry(key); if (p == null) return null; //获取旧值 V oldValue = p.value; //删除节点,而后返回旧值 deleteEntry(p); return oldValue; } final Entry<K,V> getEntry(Object key) { //若是有设置比较器,那么就优先使用比较器进行寻找 if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; //由于是红黑树,能够判断节点值的大小,而后判断是左右节点的遍历 while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; } //进行节点的删除 private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 当前节点有两个子节点,找到右子树中最小元素做为后继节点;将后继节点信息替换到当前节点 if (p.left != null && p.right != null) { //说白了就是找这个p元素相邻大小的元素,优先找大的,其次找小的 //1.找右子树的最小节点 由于上面有判断p的左右节点必须存在,因此结果确定是右子树的最小值 Entry<K,V> s = successor2(p); p.key = s.key; p.value = s.value; //吧引用指向s,吧p的值设置为s的值 //这个时候须要删除的那个节点的值变成了一个新的最靠近的值,这样就不会破坏索引树的条件,而后把那个用来替换的节点干掉便可 p = s; //替换删除的元素 } // 开始修复,优先取这个节点的左边,不然取右边做为replacement节点对象 //除非是p.right和left有一个为空,否则通常确定走的是p.right而且是个null对象 Entry<K,V> replacement = (p.left != null ? p.left : p.right); //若是要进行取代的节点为空,那么就不用操做 // 一、有两个儿子。这种状况比较复杂,但仍是比较简单。上面提到过用子节点C替代代替待删除节点D,而后删除子节点C便可。 // 二、没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。 // 三、只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。 if (replacement != null) { //把须要替换的节点的父节点设置为p的父节点 replacement.parent = p.parent; //判断p的父节点是否Wie空,或者判断p是不是做为左节点,不然判断是不是右节点 //说白了就是用replacement 取代P节点 if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; //设置p的左右和父节点为空,也就是吧p节点剥离开 p.left = p.right = p.parent = null; // 修复颜色分配,由于最后一个要删除的节点是黑色,那么删除这个节点以后,这条线的黑色节点个数确定会减去1 if (p.color == BLACK) //那么咱们就须要对这个变更过的节点进行调整 fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. root = null; } else { //通常状况是这个,也就是说吧须要删除的节点移动到末尾叶子节点,而后把key,value替换掉,最后删除调最后一个叶子便可 //而后若是叶子的颜色正好是黑色的,那么要从新修复颜色 if (p.color == BLACK) fixAfterDeletion(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; } } } static <K,V> TestTreeMap.Entry<K,V> successor2(Entry<K,V> t) { //找到右边节点的最小元素,也就是仅仅比T大一点的元素 Entry<K,V> p = t.right; while (p.left != null) p = p.left; return p; } private void fixAfterDeletion(Entry<K,V> x) { //判断替换过的节点是不是黑色,删除节点须要一直迭代,直到 x 不是根节点,且 x 的颜色是黑色 while (x != root && colorOf(x) == BLACK) { //若是这个节点是左节点 if (x == leftOf(parentOf(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; } } else { // symmetric 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); }
修改能够参考put操做,对key无影响
查找由于是二叉索引树,因此查找方式和remove中的getEntry操做相似
6.1BuildFromSorted
这函数用来根据一级拍好顺序的map构建treemap
再这个函数以前还有一个computeRedLevel函数,这个用来计算当前节点所在的层数
//计算红色节点应该在红黑树哪一层,由于二叉树,由于每层二叉树要填满的话必须是2的倍数 //每层数据叠加是1,1+2,1+2+4,1+2+4+8.。。 基本就是每层就是每层/2 //sz指树中节点的个数,为了确保是一个红黑树,那么须要把前面几层所有当作黑色,最后一层设置为红色便可 //由于sz是节点的个数,因此最后一个节点所在的层数便是红色 public static int computeRedLevel(int sz) { int level = 0; //从0开始计算0,2,6,14 //能够看出m=(m+1)*2 前一个和后一个的递推关系 每一层计算 //那么反过来就是m/2-1就是上一层的位置,最后一个m>=0的时候还要计算一次 for (int m = sz - 1; m >= 0; m = m / 2 - 1) level++; return level; } //经过迭代构造一个新的排序的map,递归将SortedMap中的元素逐个关联 //str: 若是不为空,则从流里读取key-value,defaultVal:见名知意,不为空,则value都用这个值 private void buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal) throws java.io.IOException, ClassNotFoundException { this.size = size; //递归,添加到root节点上 root = buildFromSorted(0, 0, size-1, computeRedLevel(size), it, str, defaultVal); } /** * level: 当前树的层数,注意:是从0层开始 * lo: 子树第一个元素的索引 * hi: 子树最后一个元素的索引 * redLevel: 上述红节点所在层数 * 剩下的3个就不解释了,跟上面的同样 */ private final Entry<K,V> buildFromSorted(int level, int lo, int hi, int redLevel, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal) throws java.io.IOException, ClassNotFoundException { /* * Strategy: The root is the middlemost element. To get to it, we * have to first recursively construct the entire left subtree, * so as to grab all of its elements. We can then proceed with right * subtree. * * The lo and hi arguments are the minimum and maximum * indices to pull out of the iterator or stream for current subtree. * They are not actually indexed, we just proceed sequentially, * ensuring that items are extracted in corresponding order. */ if (hi < lo) return null; //这至关于除以二,取中间位置,至关于除以2 int mid = (lo + hi) >>> 1; Entry<K,V> left = null; //子树第一个元素的索引开始到中间的位置做为左子树,右边剩下递归又右子树 if (lo < mid) //递归左边部分节点 left = buildFromSorted(level+1, lo, mid - 1, redLevel, it, str, defaultVal); K key; V value; //经过迭代器遍历全部节点 if (it != null) { if (defaultVal==null) { Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next(); key = (K)entry.getKey(); value = (V)entry.getValue(); } else { key = (K)it.next(); value = defaultVal; } } else { // use stream,经过流读取对象 key = (K) str.readObject(); value = (defaultVal != null ? defaultVal : (V) str.readObject()); } //建立中间节点 Entry<K,V> middle = new Entry<>(key, value, null); // color nodes in non-full bottommost level red if (level == redLevel) middle.color = RED; if (left != null) { middle.left = left; left.parent = middle; } //递归右边节点 if (mid < hi) { Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel, it, str, defaultVal); middle.right = right; right.parent = middle; } return middle; }
这里能够总结一点:左旋和右旋的判断主要依据是=》从任一节点到其每一个叶子的全部简单路径都包含相同数目的黑色节点。由于咱们每次新增的节点都是红色,因此这个红色的节点就会破坏原来的结构,会再红色的null节点新增一个黑色null节点,为了修复这种状况,那么就须要对父节点下的另一个节点进行修复
不存在扩容问题,二叉树嘛,更相似链表的结构
总结就是不管是新增仍是删除,再修复颜色的时候,维持从任一节点到其每一个叶子的全部简单路径都包含相同数目的黑色节点这个原则
因此通常会校验这个节点的兄弟,以及父辈的兄弟节点,至于进行右旋仍是左旋,这个参考我以前的博客的内容以及规则
参考:
https://blog.csdn.net/cyywxy/article/details/81151104https://www.jianshu.com/p/e11fe1760a3d