Java集合(十) —— TreeMap源码分析

Java集合(一) —— Collection源码分析
Java集合(二) —— ArrayList源码分析
Java集合(三) —— LinkedList源码分析
Java集合(四) —— PriorityQueue源码分析
Java集合(五) —— HashSet源码分析
Java集合(六) —— LinkedHashSet源码分析
Java集合(七) —— TreeSet源码分析
Java集合(八) —— HashMap源码分析
Java集合(九) —— LinkedHashMap源码分析
Java集合(十) —— TreeMap源码分析

1.总结

1.TreeMap数据结构:红黑树
2.有序的key-value映射,key必须可以自然排序的,否则就要自定义比较器Comparator作为参数传入
3.默认key不可以为null,因为要通过key排序

2.继承关系图

TreeMap.png

3.源码分析

3.1成员变量分析

// key的比较器
private final Comparator<? super K> comparator;
// 红黑树的根节点
private transient Entry<K,V> root;
// treeMap的大小
private transient int size = 0;
// 树修改的次数
private transient int modCount = 0;
// 键值对集合
private transient EntrySet entrySet;
// 键集合
private transient KeySet<K> navigableKeySet;
// 降序的NavigableMap
private transient NavigableMap<K,V> descendingMap;

3.2构造方法分析

public TreeMap() {
    comparator = null;
}

/**
 * 自定义比较器comparator
 */
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}

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) {
    }
}

没啥好深究的,一般都是用默认的构造方法

3.3常用方法分析

1.put方法

public V put(K key, V value) {
    // 将根节点赋值给t
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // 默认要通过比较key进行排序,所以key不能为null,否则空指针异常

        // 如果根节点为null,则新增元素节点就是根节点
        // 此时不用调整红黑树,新增结束
        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();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            // k < t.key
            if (cmp < 0)
                t = t.left;
            // k > t.key
            else if (cmp > 0)
                t = t.right;
            // k = t.key,由于key不能重复,所以这里更新节点数据
            else
                return t.setValue(value);
        } while (t != null); // 直到找到的子节点为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;
}
  • 如果红黑树为null,则之间新建一个Entry节点,并设置为根节点root
  • 检查comparator是否为null,不为null则使用comparator比较key的大小,否则使用k.compareTo(t.key)比较;遍历红黑树,找到对应的key,更新value
  • 如果没有找到,则新建一个Entry节点添加到树上,然后执行fixAfterInsertion(e),确保新增节点后仍是红黑树
  • 关于fixAfterInsertion方法调整红黑树比较复杂,这里不再展开,我会在数据结构中更新红黑树的分析
    2.putAll方法
public void putAll(Map<? extends K, ? extends V> map) {
    int mapSize = map.size();
    // 针对将一个非空的SortedMap加入空的TreeMap且比较器相同的情况做了优化
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {
        Comparator<?> c = ((SortedMap<?,?>)map).comparator();
        if (c == comparator || (c != null && c.equals(comparator))) {
            ++modCount;
            try {
                buildFromSorted(mapSize, map.entrySet().iterator(),
                                null, null);
            } catch (java.io.IOException cannotHappen) {
            } catch (ClassNotFoundException cannotHappen) {
            }
            return;
        }
    }
    // 否则,直接遍历给定map,将每一个节点添加到TreeMap上
    super.putAll(map); // 会循环调用put方法
}

3.get(key)方法

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

final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if (comparator != null)
        // 使用指定比较器遍历红黑树
        return getEntryUsingComparator(key);
    // key为null时,抛出空指针异常
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
    Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
    // 从根节点开始遍历红黑树
    while (p != null) {
        // 将给定k与树的节点的key比较
        int cmp = k.compareTo(p.key);
        if (cmp < 0) // < 0则遍历左子树
            p = p.left;
        else if (cmp > 0)
            p = p.right; // > 0则遍历右子树
        else
            return p; // 相等表示找到节点
    }
    return null; // 否则没有该节点,返回null
}

4.remove()方法

public V remove(Object key) {
    Entry<K,V> p = getEntry(key); // 根据key查找节点
    if (p == null)
        return null;

    V oldValue = p.value;
    // 删除节点
    deleteEntry(p);
    return oldValue;
}

/**
 * p:待删除节点
 */
private void deleteEntry(Entry<K,V> p) {
    modCount++;
    size--;

    if (p.left != null && p.right != null) { // p有左右节点
        // 得到p的后继节点;得到的是p的右子树的最左节点(大于当前节点的最小节点)
        Entry<K,V> s = successor(p);
        // 修改p的key和value,并将p指向s
        p.key = s.key;
        p.value = s.value;
        p = s; // 经过上述操作其实已经将p删除,p节点的内容已经变为s节点的内容
    } // p has 2 children

    // Start fixup at replacement node, if it exists.
    // p == s,replacement为p(s)的左孩子或右孩子;如果上面进入了if代码块,这里得到的一定是右孩子
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    // 1.具有孩子节点的情况
    if (replacement != null) {
        // 修改replacement的父节点指向p的父节点
        replacement.parent = p.parent;
        if (p.parent == null) // 表示删除的根节点
            // 孩子节点成为根节点
            root = replacement;
        else if (p == p.parent.left)
            // 只有进入上面的if代码块才可能进入这个代码块
            // 此时replacement为p的右孩子,将p的父节点的左孩子指向replacement
            p.parent.left  = replacement;
        else
            // 否则,p的父节点的右孩子指向replacement
            p.parent.right = replacement;

        // 将p节点删除
        p.left = p.right = p.parent = null;

        if (p.color == BLACK)
            // 如果p的颜色为黑色,需要调整红黑树的平衡
            fixAfterDeletion(replacement);
    } else if (p.parent == null) { // 删除的是根节点且只有一个节点
        root = null;
    } else { // 删除的节点没有孩子节点
        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;
        }
    }
}

TreeMap的红黑树结构操作比较复杂,没有红黑树基础建议先看看红黑树的知识。
如果不深究的话,知道TreeMap的存储结构,并且key不可以为null,默认以key的升序排序就足够了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容