澳门新葡亰娱乐网站-www.142net-欢迎您

澳门新葡亰娱乐网站是因为你还没有找到一条正确的致富之路,www.142net是将所有的游戏都汇集在一起的官方平台,因为澳门新葡亰娱乐网站这个网站当中有着大量的游戏攻略,托IP定位技术,传达终端直接到达的精准传播方式。

澳门普京平台:TreeMap源代码分析,TreeMap源码分析

来源:http://www.bhtsgq.com 作者:计算机知识 人气:112 发布时间:2019-05-30
摘要:因为TreeMap的相干知识较多,故TreeMap的辨析将会分成3篇文章来写,望大家谅解。 透过上篇小说,大家早就能够精通的摸底到treeMap插入结点的进程,那么本篇小说就来深入分析下TreeMap删

因为TreeMap的相干知识较多,故TreeMap的辨析将会分成3篇文章来写,望大家谅解。

透过上篇小说,大家早就能够精通的摸底到treeMap插入结点的进程,那么本篇小说就来深入分析下TreeMap删除2个结点时,内部数据结构爆发了怎么的转换。

TreeMap简介


周围的数据结构有数组、链表,还大概有1种结构也很布满,那正是树。前边介绍的群集类有基于数组的ArrayList,有依靠链表的LinkedList,还应该有链表和数组结合的HashMap,明天牵线基于树的TreeMap。
TreeMap基于红黑树(点击查阅树、红黑树相关内容)完毕。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。TreeMap的表征在于,所获得的结果是通过排序的。TreeMap是独步天下的含有subMap()方法的Map,它能够回到二个子树。

TreeMap是在java.util包下边,也是不改变的map集结,它的规律是“红黑树”落成的:

本篇文章先给我们介绍一下红黑树基本概念,并剖判一下在红黑树中寻找有些结点的有关源码完结。

 

TreeMap源码分析


在介绍TreeMap前先介绍Comparable和Comparator接口。
Comparable接口:

public interface Comparable<T> {

   public int compareTo(T o);
}

Comparable接口协理泛型,唯有一个办法,该方法再次来到负数、零、正数分别代表近期指标“小于”、“等于”、“大于”传入对象o。
Comparamtor接口:

public interface Comparator<T> {

    int compare (T o1, T o2);

    boolean equals (Object obj);
}
  • compare(T o1,T o二)方法相比较o一和o2多个指标,o一“大于”o二,重返正数,相等重临零,“小于”重返负数。
  • equals(Object obj)再次来到true的并世无两情形是obj也是3个相比器(Comparator)并且相比结实和此比较器的结果的深浅顺序是均等的。即comp一.equals(comp贰)意味着sgn(comp一.compare(o壹, o2))==sgn(comp贰.compare(o1, o二))。
    ** 补充:** 符号sgn(expression)表示数学上的signum函数,该函数基于expression的值是负数、零或正数,分别重临-1、0或一。
    小结一下,完结Comparable结构的类可以和别的对象进行相比较,即落到实处Comparable可以开始展览相比的类。而完毕Comparator接口的类是比较器,用于相比较多少个对象的分寸。

** 上面正式深入分析TreeMap的源码。**
既然TreeMap底层使用的是树结构,那么自然有表示节点的靶子。上面先看TreeMap中表示节点的内部类Entry。

static final class Entry<K, V> implements Map.Entry<K, V> {
    // 键值对的“键”
    K key;
    // 键值对的“值”
    V value;
    // 左孩子
    Entry<K, V> left = null;
    // 右孩子
    Entry<K, V> right = null;
    // 父节点
    Entry<K, V> parent;
    // 红黑树的节点表示颜色的属性
    boolean color = BLACK;

    /**
     * 根据给定的键、值、父节点构造一个节点,颜色为默认的黑色
     */
    Entry (K key, V value, Entry<K, V> parent) {
        this.key = key;
        this.value = value;
        this.parent = parent;
    }

    // 获取节点的key
    public K getKey () {
        return key;
    }

    // 获取节点的value
    public V getValue () {
        return value;
    }

    /**
     * 修改并返回当前节点的value
     */
    public V setValue (V value) {
        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    // 判断节点相等的方法(两个节点为同一类型且key值和value值都相等时两个节点相等)
    public boolean equals (Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
        return valEquals (key,
                          e.getKey ()) && valEquals (value,
                                                     e.getValue ());
    }

    // 节点的哈希值计算方法
    public int hashCode () {
        int keyHash = (key == null ? 0 : key.hashCode ());
        int valueHash = (value == null ? 0 : value.hashCode ());
        return keyHash ^ valueHash;
    }

    public String toString () {
        return key   "="   value;
    }
}

上边的Entry类相比轻易,落成了树节点的不可缺少内容,提供了hashCode方法等。

上面看TreeMap类的概念。

public class TreeMap<K, V>
        extends AbstractMap<K, V>
        implements NavigableMap<K, V>, Cloneable, java.io.Serializable

澳门普京平台:TreeMap源代码分析,TreeMap源码分析。NavigableMap接口增添的SortedMap,具备了针对性给定寻找指标重回最周围相配项的领航方法。方法lowerEntry、floorEntry、ceilingEntry和higherEntry分别重临与小于、小于等于、大于等于、大于给定键的键关联的Map.Entry对象,假如不设有那样的键,则赶回null。类似地,方法lowerKey、floorKey、ceilingKey和higherKey只回去关联的键。全体那么些艺术是为搜索条款而不是遍历条款而布署的(后边会相继介绍这么些办法)。

下面是TreeMap的属性:

// 用于保持顺序的比较器,如果为空的话使用自然顺保持Key的顺序
    private final Comparator<? super K> comparator;
    // 根节点
    private transient Entry<K,V> root = null;
    // 树中的节点数量
    private transient int size = 0;
    // 多次在集合类中提到了,用于举了结构行的改变次数
    private transient int modCount = 0;

表明中早已交给了品质的表明,下边看TreeMap的构造方法。

// 构造方法一,默认的构造方法,comparator为空,即采用自然顺序维持TreeMap中节点的顺序
public TreeMap() {
    comparator = null;
}
// 构造方法二,提供指定的比较器
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
// 构造方法三,采用自然序维持TreeMap中节点的顺序,同时将传入的Map中的内容添加到TreeMap中
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}
/** 
* 构造方法四,接收SortedMap参数,根据SortedMap的比较器维持TreeMap中的节点顺序
* 同时通过buildFromSorted(int size, Iterator it, java.io.ObjectInputStream str, V defaultVal)方
* 法将SortedMap中的内容添加到TreeMap中
*/
public TreeMap(SortedMap<K, ? extends V> m) {
    comparator = m.comparator();
    try {
        buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
    } catch (java.io.IOException cannotHappen) {
    } catch (ClassNotFoundException cannotHappen) {
    }
}

TreeMap提供了多个构造方法,已经在疏解中提交表达。构造方法中关系到的法子在下文中会有介绍。

上面从put/get方法开首,每种深入分析TreeMap的主意。
put(K key, V value)

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
        //如果根节点为null,将传入的键值对构造成根节点(根节点没有父节点,所以传入的父节点为null)
            root = new Entry<K,V>(key, value, null);
            size = 1;
            modCount  ;
            return null;
        }
        // 记录比较结果
        int cmp;
        Entry<K,V> parent;
        // 分割比较器和可比较接口的处理
        Comparator<? super K> cpr = comparator;
        // 有比较器的处理
        if (cpr != null) {
            // do while实现在root为根节点移动寻找传入键值对需要插入的位置
            do {
                // 记录将要被掺入新的键值对将要节点(即新节点的父节点)
                parent = t;
                // 使用比较器比较父节点和插入键值对的key值的大小
                cmp = cpr.compare(key, t.key);
                // 插入的key较大
                if (cmp < 0)
                    t = t.left;
                // 插入的key较小
                else if (cmp > 0)
                    t = t.right;
                // key值相等,替换并返回t节点的value(put方法结束)
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 没有比较器的处理
        else {
            // key为null抛出NullPointerException异常
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            // 与if中的do while类似,只是比较的方式不同
            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);
        }
        // 没有找到key相同的节点才会有下面的操作
        // 根据传入的键值对和找到的“父节点”创建新节点
        Entry<K,V> e = new Entry<K,V>(key, value, parent);
        // 根据最后一次的判断结果确认新节点是“父节点”的左孩子还是又孩子
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // 对加入新节点的树进行调整
        fixAfterInsertion(e);
        // 记录size和modCount
        size  ;
        modCount  ;
        // 因为是插入新节点,所以返回的是null
        return null;
    }

率先一点属性是TreeMap的put方法和别的Map的put方法一样,向Map中参预键值对,若原先“键(key)”已经存在则替换“值(value)”,并赶回原先的值。
在put(K key,V value)方法的最终调用了fixAfterInsertion(Entry<K,V> x)方法,那几个主意担任在插入节点后调节树结谈判设色,以满意红黑树的要求。

  1. 每三个节点依旧着成灰黄,只怕着成葡萄紫。
  2. 根是栗褐的。
  3. 若果1个节点是青白的,那么它的子节点必须是浅灰褐的。
  4. 一个节点到一个null引用的每一条路线必须含有一样数量的品绿节点。

在看fixAfterInsertion(Entry<K,V> x)方法前先看2个红黑树的原委:红黑树不是从严的平衡二叉树,它并不严刻的承接保险左右子树的万丈差不超越1,但红黑树中度依旧是平均log(n),且最坏景况中度不会当先2log(n),所以它终于平衡树。

下边看具体贯彻代码。
fixAfterInsertion(Entry<K,V> x)

private void fixAfterInsertion(Entry<K,V> x) {
    // 插入节点默认为红色
    x.color = RED;
    // 循环条件是x不为空、不是根节点、父节点的颜色是红色(如果父节点不是红色,则没有连续的红色节点,不再调整)
    while (x != null && x != root && x.parent.color == RED) {
        // x节点的父节点p(记作p)是其父节点pp(p的父节点,记作pp)的左孩子(pp的左孩子)
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            // 获取pp节点的右孩子r
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            // pp右孩子的颜色是红色(colorOf(Entry e)方法在e为空时返回BLACK),不需要进行旋转操作(因为红黑树不是严格的平衡二叉树)
            if (colorOf(y) == RED) {
                // 将父节点设置为黑色
                setColor(parentOf(x), BLACK);
                // y节点,即r设置成黑色
                setColor(y, BLACK);
                // pp节点设置成红色
                setColor(parentOf(parentOf(x)), RED);
                // x“移动”到pp节点
                x = parentOf(parentOf(x));
            } else {//父亲的兄弟是黑色的,这时需要进行旋转操作,根据是“内部”还是“外部”的情况决定是双旋转还是单旋转
                // x节点是父节点的右孩子(因为上面已近确认p是pp的左孩子,所以这是一个“内部,左-右”插入的情况,需要进行双旋转处理)
                if (x == rightOf(parentOf(x))) {
                    // x移动到它的父节点
                    x = parentOf(x);
                    // 左旋操作
                    rotateLeft(x);
                }
                // x的父节点设置成黑色
                setColor(parentOf(x), BLACK);
                // x的父节点的父节点设置成红色
                setColor(parentOf(parentOf(x)), RED);
                // 右旋操作
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            // 获取x的父节点(记作p)的父节点(记作pp)的左孩子
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            // y节点是红色的
            if (colorOf(y) == RED) {
                // x的父节点,即p节点,设置成黑色
                setColor(parentOf(x), BLACK);
                // y节点设置成黑色
                setColor(y, BLACK);
                // pp节点设置成红色
                setColor(parentOf(parentOf(x)), RED);
                // x移动到pp节点
                x = parentOf(parentOf(x));
            } else {
                // x是父节点的左孩子(因为上面已近确认p是pp的右孩子,所以这是一个“内部,右-左”插入的情况,需要进行双旋转处理),
                if (x == leftOf(parentOf(x))) {
                    // x移动到父节点
                    x = parentOf(x);
                    // 右旋操作
                    rotateRight(x);
                }
                // x的父节点设置成黑色
                setColor(parentOf(x), BLACK);
                // x的父节点的父节点设置成红色
                setColor(parentOf(parentOf(x)), RED);
                // 左旋操作
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    // 根节点为黑色
    root.color = BLACK;
}

fixAfterInsertion(Entry<K,V> x)方法涉及到了左旋和右旋的操作,上面是左旋的代码及暗指图(右旋操作看似,就不交付代码和暗指图了)。

// 左旋操作
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;
    }
}

澳门普京平台 1

看完put操作,上边来看get操作相关的剧情。
get(Object key)

    public V get (Object key) {
        Entry<K, V> p = getEntry (key);
        return (p == null ? null : p.value);
    }

get(Object key)通过key获取相应的value,它经过调用getEntry(Object key)获取节点,若节点为null则赶回null,不然再次回到节点的value值。

上面是getEntry(Object key)的从头到尾的经过,来看它是怎么寻觅节点的。
getEntry(Object key)

final Entry<K,V> getEntry(Object key) {
    // 如果有比较器,返回getEntryUsingComparator(Object key)的结果
    if (comparator != null)
        return getEntryUsingComparator(key);
    // 查找的key为null,抛出NullPointerException
    if (key == null)
        throw new NullPointerException();
    // 如果没有比较器,而是实现了可比较接口
    Comparable<? super K> k = (Comparable<? super K>) key;
    // 获取根节点
    Entry<K,V> p = root;
    // 对树进行遍历查找节点
    while (p != null) {
        // 把key和当前节点的key进行比较
        int cmp = k.compareTo(p.key);
        // key小于当前节点的key
        if (cmp < 0)
            // p “移动”到左节点上
            p = p.left;
        // key大于当前节点的key
        else if (cmp > 0)
            // p “移动”到右节点上
p = p.right;
        // key值相等则当前节点就是要找的节点
        else
            // 返回找到的节点
            return p;
        }
    // 没找到则返回null
    return null;
}

地方根本是拍卖实现了可正如接口的情形,而有比较器的情况在getEntryUsingComparator(Object key)中处理了,上面来看拍卖的代码。
getEntryUsingComparator(Object key)

final Entry<K,V> getEntryUsingComparator(Object key) {
    K k = (K) key;
    // 获取比较器
Comparator<? super K> cpr = comparator;
// 其实在调用此方法的get(Object key)中已经对比较器为null的情况进行判断,这里是防御性的判断
if (cpr != null) {
    // 获取根节点
        Entry<K,V> p = root;
        // 遍历树
        while (p != null) {
            // 获取key和当前节点的key的比较结果
            int cmp = cpr.compare(k, p.key);
            // 查找的key值较小
            if (cmp < 0)
                // p“移动”到左孩子
                p = p.left;
            // 查找的key值较大
            else if (cmp > 0)
                // p“移动”到右节点
                p = p.right;
            // key值相等
            else
                // 返回找到的节点
                return p;
        }
}
// 没找到key值对应的节点,返回null
    return null;
}

看完增多(put)和获得(get),上面来看删除(remove、clear)。
remove(Object key)

public V remove(Object key) {
    // 通过getEntry(Object key)获取节点 getEntry(Object key)方法已经在上面介绍过了
Entry<K,V> p = getEntry(key);
// 指定key的节点不存在,返回null
    if (p == null)
        return null;
    // 获取节点的value
V oldValue = p.value;
// 删除节点
deleteEntry(p);
// 返回节点的内容
    return oldValue;
}

诚然完成删除节点的故事情节在deleteEntry(Entry e)中,涉及到树结构的调动等。remove(Object key)只是赢得要刨除的节点并赶回被剔除节点的value。下边来看deleteEntry(Entry e)的从头到尾的经过。
deleteEntry(Entry e)

private void deleteEntry(Entry<K,V> p) {
// 记录树结构的修改次数
modCount  ;
// 记录树中节点的个数
    size--;

// p有左右两个孩子的情况  标记①
if (p.left != null && p.right != null) {
        // 获取继承者节点(有两个孩子的情况下,继承者肯定是右孩子或右孩子的最左子孙)
        Entry<K,V> s = successor (p);
        // 使用继承者s替换要被删除的节点p,将继承者的key和value复制到p节点,之后将p指向继承者
        p.key = s.key;
        p.value = s.value;
        p = s;
    } 

// Start fixup at replacement node, if it exists.
// 开始修复被移除节点处的树结构
// 如果p有左孩子,取左孩子,否则取右孩子    标记②
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    if (replacement != null) {
        // Link replacement to parent
        replacement.parent = p.parent;
        // p节点没有父节点,即p节点是根节点
        if (p.parent == null)
            // 将根节点替换为replacement节点
            root = replacement;
        // p是其父节点的左孩子
        else if (p == p.parent.left)
            // 将p的父节点的left引用指向replacement
            // 这步操作实现了删除p的父节点到p节点的引用
            p.parent.left  = replacement;
        else
            // 如果p是其父节点的右孩子,将父节点的right引用指向replacement
            p.parent.right = replacement;
        // 解除p节点到其左右孩子和父节点的引用
        p.left = p.right = p.parent = null;
        if (p.color == BLACK)
            // 在删除节点后修复红黑树的颜色分配
            fixAfterDeletion(replacement);
} else if (p.parent == null) { 
/* 进入这块代码则说明p节点就是根节点(这块比较难理解,如果标记①处p有左右孩子,则找到的继承节点s是p的一个祖先节点或右孩子或右孩子的最左子孙节点,他们要么有孩子节点,要么有父节点,所以如果进入这块代码,则说明标记①除的p节点没有左右两个孩子。没有左右孩子,则有没有孩子、有一个右孩子、有一个左孩子三种情况,三种情况中只有没有孩子的情况会使标记②的if判断不通过,所以p节点只能是没有孩子,加上这里的判断,p没有父节点,所以p是一个独立节点,也是树种的唯一节点……有点难理解,只能解释到这里了,读者只能结合注释慢慢体会了),所以将根节点设置为null即实现了对该节点的删除 */
        root = null;
} else { /* 标记②的if判断没有通过说明被删除节点没有孩子,或它有两个孩子但它的继承者没有孩子。如果是被删除节点没有孩子,说明p是个叶子节点,则不需要找继承者,直接删除该节点。如果是有两个孩子,那么继承者肯定是右孩子或右孩子的最左子孙 */
        if (p.color == BLACK)
            // 调整树结构
            fixAfterDeletion(p);
        // 这个判断也一定会通过,因为p.parent如果不是null则在上面的else if块中已经被处理
        if (p.parent != null) {
            // p是一个左孩子
            if (p == p.parent.left)
                // 删除父节点对p的引用
                p.parent.left = null;
            else if (p == p.parent.right)// p是一个右孩子
                // 删除父节点对p的引用
                p.parent.right = null;
            // 删除p节点对父节点的引用
            p.parent = null;
        }
    }
}

deleteEntry(Entry e)方法中首要有五个措施调用需求深入分析:successor(Entry<K,V> t)和fixAfterDeletion(Entry<K,V> x)。

successor(Entry<K,V> t)重回钦点节点的子孙后代。分二种情况管理,第一。t节点是个空节点:再次来到null;第二,t有右孩子:找到t的右孩子中的最左子孙节点,假使右孩子从未左孩子则赶回右节点,不然再次来到找到的最左子孙节点;第一,t未有右孩子:沿着向上(向跟节点方向)找到第4个自个儿是2个左孩子的节点或根节点,再次来到找到的节点。上面是现实性代码解析的笺注。

static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    // 如果t本身是一个空节点,返回null
    if (t == null)
        return null;
    // 如果t有右孩子,找到右孩子的最左子孙节点
    else if (t.right != null) {
        Entry<K,V> p = t.right;
        // 获取p节点最左的子孙节点,如果存在的话
        while (p.left != null)
            p = p.left;
        // 返回找到的继承节点
        return p;
    } else {//t不为null且没有右孩子
        Entry<K,V> p = t.parent;
        Entry<K,V> ch = t;
       // // 沿着右孩子向上查找继承者,直到根节点或找到节点ch是其父节点的左孩子的节点
        while (p != null && ch == p.right) {
            ch = p;
            p = p.parent;
        }
        return p;
    }
}

与增加节点之后的修补类似的是,TreeMap 删除节点之后也急需张开类似的修复操作,通过这种修复来保证该排序贰叉树依然知足红黑树特征。我们能够参照插入节点之后的修补来剖判删除之后的修复。TreeMap 在剔除之后的修补操作由 fixAfterDeletion(Entry<K,V> x) 方法提供,该方法源代码如下:

private void fixAfterDeletion(Entry<K,V> x) {
    // 循环处理,条件为x不是root节点且是黑色的(因为红色不会对红黑树的性质造成破坏,所以不需要调整)
while (x != root && colorOf(x) == BLACK) {
    // x是一个左孩子
        if (x == leftOf(parentOf(x))) {
            // 获取x的兄弟节点sib
            Entry<K,V> sib = rightOf(parentOf(x));
            // sib是红色的
            if (colorOf(sib) == RED) {
                // 将sib设置为黑色
                setColor(sib, BLACK);
                // 将父节点设置成红色
                setColor(parentOf(x), RED);
                // 左旋父节点
                rotateLeft(parentOf(x));
                // sib移动到旋转后x的父节点p的右孩子(参见左旋示意图,获取的节点是旋转前p的右孩子r的左孩子rl)
                sib = rightOf(parentOf(x));
            }
            // sib的两个孩子的颜色都是黑色(null返回黑色)
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                // 将sib设置成红色
                setColor(sib, RED);
                // x移动到x的父节点
                x = parentOf(x);
            } else {// sib的左右孩子都是黑色的不成立
                // sib的右孩子是黑色的
                if (colorOf(rightOf(sib)) == BLACK) {
                    // 将sib的左孩子设置成黑色
                    setColor(leftOf(sib), BLACK);
                    // sib节点设置成红色
                    setColor(sib, RED);
                    // 右旋操作
                    rotateRight(sib);
                    // sib移动到旋转后x父节点的右孩子
                    sib = rightOf(parentOf(x));
                }
                // sib设置成和x的父节点一样的颜色
                setColor(sib, colorOf(parentOf(x)));
                // x的父节点设置成黑色
                setColor(parentOf(x), BLACK);
                // sib的右孩子设置成黑色
                setColor(rightOf(sib), BLACK);
                // 左旋操作
                rotateLeft(parentOf(x));
                // 设置调整完的条件:x = root跳出循环
                x = root;
            }
        } else { // x是一个右孩子
            // 获取x的兄弟节点
            Entry<K,V> sib = leftOf(parentOf(x));
            // 如果sib是红色的
            if (colorOf(sib) == RED) {
                // 将sib设置为黑色
                setColor(sib, BLACK);
                // 将x的父节点设置成红色
                setColor(parentOf(x), RED);
                // 右旋
                rotateRight(parentOf(x));
                // sib移动到旋转后x父节点的左孩子
                sib = leftOf(parentOf(x));
            }
            // sib的两个孩子的颜色都是黑色(null返回黑色)
            if (colorOf(rightOf(sib)) == BLACK &&
                colorOf(leftOf(sib)) == BLACK) {
                // sib设置为红色
                setColor(sib, RED);
                // x移动到x的父节点
                x = parentOf(x);
            } else { // sib的两个孩子的颜色都是黑色(null返回黑色)不成立
                // sib的左孩子是黑色的,或者没有左孩子
                if (colorOf(leftOf(sib)) == BLACK) {
                    // 将sib的右孩子设置成黑色
                    setColor(rightOf(sib), BLACK);
                    // sib节点设置成红色
                    setColor(sib, RED);
                    // 左旋
                    rotateLeft(sib);
                    // sib移动到x父节点的左孩子
                    sib = leftOf(parentOf(x));
                }
                // sib设置成和x的父节点一个颜色
                setColor(sib, colorOf(parentOf(x)));
                // x的父节点设置成黑色
                setColor(parentOf(x), BLACK);
                // sib的左孩子设置成黑色
                setColor(leftOf(sib), BLACK);
                // 右旋
                rotateRight(parentOf(x));
                // 设置跳出循环的标识
                x = root;
            }
        }
    }
    // 将x设置为黑色
    setColor(x, BLACK);
}

光看调治的代码,一大堆设置颜色,还会有左旋和右旋,特其余指雁为羹,上面是多个布局红黑树的视屏,蕴含了着色和旋转。
http://v.youku.com/v_show/id_XMjI3NjM0MTgw.html

clear()

    public void clear () {
        modCount  ;
        size = 0;
        root = null;
    }

clear()方法比不会细小略,只是记录组织修改次数,将size修改为0,将root设置为null,那样就没办法通过root访问树的别样节点,所以数的剧情会被GC回收。

增进(修改)、获取、删除的原码都早已看了,下面看决断是不是包罗的措施。
containKey(Object key)

    public boolean containsKey (Object key) {
        return getEntry (key) != null;
    }

本条情势剖断获得key对应的节点是还是不是为空,getEntry(Object key)方法已经在上面介绍过了。

contain(Object value)

public boolean containsValue(Object value) {
    // 通过e = successor(e)实现对树的遍历
    for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
    // 判断节点值是否和value相等
        if (valEquals(value, e.value))
            return true;
    // 默认返回false
    return false;
}

contain(Object value)涉及到了getFirstEntry()方法和successor(Entry<K,V> e)。getFirstEntry()是获得第二个节点,successor(Entry<K,V> e)是获得节点e的子孙后代,在for循环中相配使用getFirstEntry()方法和successor(Entry<K,V> e)及e!=null是遍历树的1种办法。

上面介绍getFirstEntry()方法。
getFirstEntry()

final Entry<K,V> getFirstEntry() {
    Entry<K,V> p = root;
    if (p != null)
        while (p.left != null)
            p = p.left;
    return p;
}

从名字上看是获取第二个节点,实际是获得的整棵树中“最左”的节点(第一个节点具体指哪一个节点和树的遍历次序有关,就算是先根遍历,则率先个节点是根节点)。又因为红黑树是排序的树,所以“最左”的节点也是值最小的节点。

地点是getFirstEntry()方法,上边介绍getLastEntry()方法。
getLastEntry()

final Entry<K,V> getLastEntry() {
    Entry<K,V> p = root;
    if (p != null)
        while (p.right != null)
            p = p.right;
    return p;
}

getLastEntry()和getFirstEntry()对应,获取的是“最右”的节点。
TreeMap中提供了得到并移除最小和最大节点的七个点子:pollFirstEntry()和pollLastEntry()。
pollFirstEntry()

public Map.Entry<K,V> pollFirstEntry() {
    Entry<K,V> p = getFirstEntry();
    Map.Entry<K,V> result = exportEntry(p);
    if (p != null)
        deleteEntry(p);
    return result;
}

pollLastEntry()

public Map.Entry<K,V> pollLastEntry() {
    Entry<K,V> p = getLastEntry();
    Map.Entry<K,V> result = exportEntry(p);
    if (p != null)
        deleteEntry(p);
    return result;
}

pollFirstEntry()和pollLastEntry()分别通过getFirstEntry()和getLastEntry()获取节点,exportEntry(TreeMap.Entry<K,V> e)应该是保留这些目的用于在剔除这一个节点后回去。具体贯彻看上边包车型客车代码。

    static <K, V> Map.Entry<K, V> exportEntry (TreeMap.Entry<K, V> e) {
        return e == null ? null :
                new AbstractMap.SimpleImmutableEntry<K, V> (e);
    }

回到了贰个SimpleImmutableEntry对象,调用的构造方法如下:

    public SimpleImmutableEntry (Entry<? extends K, ? extends V> entry) {
        this.key = entry.getKey ();
        this.value = entry.getValue ();
    }

能够看来重回的节点内容只含有key和value。

下边看其余实际的获取键、值、键值对的艺术。

    public Map.Entry<K, V> ceilingEntry (K key) {
        return exportEntry (getCeilingEntry (key));
    }

    public K ceilingKey (K key) {
        return keyOrNull (getCeilingEntry (key));
    }

地点那多个格局相当的粗略,只是对exportEntry和keyOrNull的调用。keyOrNull遵照传入的Entry是不是为null,接纳格局null或Entry的key。

// 获取最小的节点的key
public K firstKey() {
    return key(getFirstEntry());
}
// 获取最大节点的key
public K lastKey() {
    return key(getLastEntry());
}
// 获取最小的键值对
public Map.Entry<K,V> firstEntry() {
    return exportEntry(getFirstEntry());
}
// 获取最大的键值对
public Map.Entry<K,V> lastEntry() {
    return exportEntry(getLastEntry());
}

那多少个格局涉及到的内容都在上边介绍过了,就不在表达了。

public Map.Entry<K,V> floorEntry(K key) {
    return exportEntry(getFloorEntry(key));
}
public K floorKey(K key) {
    return keyOrNull(getFloorEntry(key));
}
public Map.Entry<K,V> higherEntry(K key) {
    return exportEntry(getHigherEntry(key));
}
public K higherKey(K key) {
    return keyOrNull(getHigherEntry(key));
}

那多少个获得key的Entry的办法都是对getFloorEntry和getHigherEntry的拍卖。上边介绍这七个方法。

getFloorEntry(K key)

final Entry<K,V> getFloorEntry(K key) {
    // 获取根节点
Entry<K,V> p = root;
// 不是空树,最树进行遍历
    while (p != null) {
        int cmp = compare(key, p.key);
        // key较大
        if (cmp > 0) {
            // 找到节点有右孩子,则继续向右孩子遍历
            if (p.right != null)
                p = p.right;
            else// 没有右孩子,那么p节点就是树中比key值比传入key值小且最接近传入key的节点,就是要找的节点
                return p;
        } else if (cmp < 0) {// key值较小
            // 有左孩子向左孩子遍历
            if (p.left != null) {
                p = p.left;
            } else {// 没有左孩子,这个节点比key值大,返回内容是向上寻找到的根节点或比传入key值小的最后一个节点(这块比较难理解,仔细模拟寻找节点的过程就会明白)
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.left) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } else // key值相等
            return p;
    }
    return null;
}

getHigherEntry(K key)

final Entry<K,V> getHigherEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else {
            if (p.right != null) {
                p = p.right;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        }
    }
    return null;
}

getFloorEntry和getHigherEntry方法遍历和搜索节点的法子类似,差异在于getFloorEntry搜索的是自愧不比等于,优先再次来到小于的节点,而getHigherEntry搜索的是严格大于的节点,不包涵等于的地方。

上述内容是TreeMap的根底措施,TreeMap的内部类及关联到中间类的秘诀等都就要《TreeMap源码深入分析——深刻深入分析》中给出。

运用了红黑二叉树的构造来存款和储蓄每一种Entry

 

TreeMap删除有些结点的源码深入分析


 

 

TreeMap中的成分:

TreeMap是啥

澳门普京平台 2澳门普京平台 3

//比较器
private final Comparator<? super K> comparator;
//根节点
private transient Entry<K,V> root = null;
//map的中的entry数量
private transient int size = 0;
//map修改的次数
private transient int modCount = 0;

从TreeMap的类名上就可以清楚它的平底存款和储蓄结构其实是红黑树。首先简要介绍一下红黑树的连带文化,以便通晓后续内容。

 1     /**
 2      * 删除节点,并平衡红黑树的操作
 3      * 
 4      * @Param Entry<K,V> p  要删除的节点Entry
 5      */
 6     private void deleteEntry(Entry<K,V> p) {
 7         modCount  ;
 8         size--;  //节点总数-1
 9 
10         if (p.left != null && p.right != null) {  //当前要删除的节点左右子节点都不为空
11             Entry<K,V> s = successor(p);  //找到一个待删除节点的继承者节点s
12             //将指向s节点,后续所有对p的节点判断其实都是对s节点判断
13             p.key = s.key;
14             p.value = s.value;
15             p = s;
16         }
17 
18         //替代节点选择为当前被删除节点的左子节点(优先)或右子节点
19         Entry<K,V> replacement = (p.left != null ? p.left : p.right);
20 
21         if (replacement != null) {  //替代节点(被删除节点的子节点)不为空
22             
23             replacement.parent = p.parent;  //将替代节点的父节点指向被删除节点的父节点
24             if (p.parent == null)  //如果被删除节点的父节点为null (即被删除的节点就是树的根节点,且根节点下面还有其他节点)
25                 root = replacement;  //将根节点设置为替换节点
26             else if (p == p.parent.left)  //如果原先被删除节点是左子树 插入
27                 p.parent.left  = replacement;  //则将替换节点也保持左子树插入(将替换节点与被删除节点的父节点左子节点建立引用)
28             else   //如果原先被删除节点是右子树 插入
29                 p.parent.right = replacement;  //则将替换节点也保持右子树插入(将替换节点与被删除节点的父节点右子节点建立引用)
30 
31             //将被删除节点的左子节点、右子节点、父节点引用全部置为null
32             p.left = p.right = p.parent = null;
33 
34             //删除后要执行后续的保证红黑树规则的操作
35             if (p.color == BLACK)  //如果被删除节点是黑色的
36                 fixAfterDeletion(replacement);  //调用删除后修正红黑树规则的方法
37         } else if (p.parent == null) { //被删除节点就是根节点,且整个树中就只有一个根节点的情况
38             root = null;  //将根节点置为null(此时整个树中就没有节点了)
39         } else {  //被删除节点没有子节点可替代的情况 (被删除节点是叶子节点)
40             if (p.color == BLACK)   //如果被删除节点是黑色的
41                 fixAfterDeletion(p);  //调用删除后修正红黑树规则的方法
42 
43             if (p.parent != null) {  //如果被删除节点的父节点不为null
44                 if (p == p.parent.left) //如果原先被删除节点是左子树 插入
45                     p.parent.left = null;  //删除节点后将被删除节点的父节点的左子节点置为null
46                 else if (p == p.parent.right)  //如果原先被删除节点是右子树 插入
47                     p.parent.right = null;  //删除节点后将被删除节点的父节点的右子节点置为null
48                 p.parent = null;  //将被删除节点的父节点引用置为null(即将被删除节点从树中移除)
49             }
50         }
51     }

如何是红黑树?先放一张红黑树的暗示图看看:

View Code

澳门普京平台 4

源码逻辑相当的粗略,首要就是分成删除结点有子结点和无子结点,而无子结点又分为删除的是根结点或叶子结点那二种情景

注:图片出处为 和讯 —— 三月的仓颉

然后1旦被删除结点是深紫酱色的,那么要专注下可能继任者和被去除结点的父结点都以新民主主义革命的意况,此时急需做平衡红黑树的操作。

 

此处须求留意的情势有八个: 第3一行的 successor() 方法  以及  第二6行的  fixAfterDeletion() 方法。

大致表明一(Wissu)下树的相干术语的含义:

 

一.根结点(即0026结点):整个树结构图中最上面的贰个结点,别的具有的结点都以由该结点延伸出来的。

个别贴一下那五个措施的源码:

二.父结点:在树的光景结构中,有连续关系并处于上方的结点。譬如002六是00一7和00四一的父结点,001七是001贰和00贰1的父结点等等。

 1     /**
 2      * 返回被删除节点的继承者节点
 3      */
 4     static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
 5         if (t == null)  //如果被删除节点为空,则直接返回null
 6             return null;
 7         else if (t.right != null) {  //如果被删除节点的右子节点不为空
 8             Entry<K,V> p = t.right;  //将被删除节点的右子节点记录下来
 9             while (p.left != null)  //从该节点开始循环向下查找左子节点,直至找到叶子节点后返回该叶子节点
10                 p = p.left;
11             return p;
12         } else {  //如果被删除节点的右子节点为空
13             Entry<K,V> p = t.parent;  //将被删除节点的父节点用p变量记录
14             Entry<K,V> ch = t;   //被删除节点用ch变量记录
15             while (p != null && ch == p.right) {//从被删除节点开始循环向上查找父节点,直到父节点为空或者父节点没有右子节点,返回该父节点
16                 ch = p;
17                 p = p.parent;
18             }
19             return p;
20         }
21     }

三.子结点:在树的前后结构中,有连接关系并处在下方的结点。譬喻0017是00二陆的左子结点,004壹是002陆的右子结点;0030是00四1的左子结点,00四7是00肆壹的右子结点。

 

 并且左子节点是自愧不比父结点的,而右子节点一般是赶上父结点的。举例0012比00一7小,而00十比001二小;同样00四一比0026大,并且0047也比00肆一大。

澳门普京平台 5澳门普京平台 6

四.小伙子结点:属于同三个父结点的结点相互配为兄弟结点。比方00一7和00四一同属于00贰六的子结点,则00一7和00四壹就是兄弟节点。

 1    /** 
 2      * 树删除一个节点后,将其根据红黑树的规则进行修正
 3      * @param x  当前删除的节点
 4      */
 5     private void fixAfterDeletion(Entry<K,V> x) {
 6        //循环遍历,x刚开始为被删除的节点
 7         while (x != root && colorOf(x) == BLACK) {  //如果当前遍历到的节点不是根节点且为黑色
 8             if (x == leftOf(parentOf(x))) {  //如果当前遍历到的节点是其父节点的左子节点
 9                 Entry<K,V> sib = rightOf(parentOf(x));  //将当前遍历到的节点的父节点的右子节点用sib变量保存(即和当前节点平级的另一个节点)
10 
11                 if (colorOf(sib) == RED) {  //如果sib引用的节点是红色的
12                     setColor(sib, BLACK);  //将sib引用的节点设置为黑色
13                     setColor(parentOf(x), RED);  //将当前遍历到节点的父节点设置为红色
14                     rotateLeft(parentOf(x));  //对当前遍历到节点的父节点进行一次左旋操作
15                     sib = rightOf(parentOf(x)); //sib引用的节点变更为旋转后被删除节点的父节点的右子节点
16                 }
17 
18                 if (colorOf(leftOf(sib))  == BLACK &&
19                     colorOf(rightOf(sib)) == BLACK) { //如果sib引用节点的左、右子节点都是黑色的
20                     setColor(sib, RED);  //将sib引用的节点设置为红色
21                     x = parentOf(x);  //下一次遍历的节点变更为当前遍历到节点的父节点
22                 } else {  //如果sib引用节点的左、右子节点不全是黑色的
23                     if (colorOf(rightOf(sib)) == BLACK) {  //如果sib引用节点的右子节点是黑色的
24                         setColor(leftOf(sib), BLACK);  //将sib引用节点的左子节点设置为黑色
25                         setColor(sib, RED);   //sib引用节点设置为红色
26                         rotateRight(sib);  //对sib节点进行一次右旋操作
27                         sib = rightOf(parentOf(x)); //sib引用的节点变更为当前遍历到的节点的父节点的右子节点
28                     }
29                     setColor(sib, colorOf(parentOf(x)));  //将sib引用节点的颜色设置为 当前遍历到节点的父节点 一样的颜色
30                     setColor(parentOf(x), BLACK);  //将当前遍历到节点的父节点设置为黑色
31                     setColor(rightOf(sib), BLACK);  //将sib引用节点的右子节点设置为黑色
32                     rotateLeft(parentOf(x));  //对当前遍历到的节点的父节点进行一次左旋操作
33                     x = root;  //下一次遍历的节点变更为根节点
34                 }
35             } else { // 当前遍历到的节点是其父节点的右子节点,和上述情况相似,不再赘述
36                 Entry<K,V> sib = leftOf(parentOf(x));
37 
38                 if (colorOf(sib) == RED) {
39                     setColor(sib, BLACK);
40                     setColor(parentOf(x), RED);
41                     rotateRight(parentOf(x));
42                     sib = leftOf(parentOf(x));
43                 }
44 
45                 if (colorOf(rightOf(sib)) == BLACK &&
46                     colorOf(leftOf(sib)) == BLACK) {
47                     setColor(sib, RED);
48                     x = parentOf(x);
49                 } else {
50                     if (colorOf(leftOf(sib)) == BLACK) {
51                         setColor(rightOf(sib), BLACK);
52                         setColor(sib, RED);
53                         rotateLeft(sib);
54                         sib = leftOf(parentOf(x));
55                     }
56                     setColor(sib, colorOf(parentOf(x)));
57                     setColor(parentOf(x), BLACK);
58                     setColor(leftOf(sib), BLACK);
59                     rotateRight(parentOf(x));
60                     x = root;
61                 }
62             }
63         }
64 
65         setColor(x, BLACK);
66     }

5.叶子结点:没有任何子结点的结点称为叶子节点。如上海教室中负有的NULL LEAF结点都以卡牌结点。

View Code

陆.2叉树:每种节点最六只有几个子结点的树结构称为2叉树。如上海体育场所就是一种二叉树的数据结构。

大概光看那八个办法的源码不太好理解,上面就以删除几个结点的实例,一步一步追踪下源码看看数据结构到底产生了如何变化。

树的片段中坚术语就介绍到那边,其余的请同学们自行查阅资料学习吧,这里就不再多说了 : )

 

本文由澳门新葡亰发布于计算机知识,转载请注明出处:澳门普京平台:TreeMap源代码分析,TreeMap源码分析

关键词: 新萄京 Java 进阶 JAVA资料 java 源代码学习

上一篇:责任链模式

下一篇:没有了

最火资讯