JDK之TreeMap源码解析

15 篇文章 0 订阅
13 篇文章 0 订阅

刚入java不久的程序猿,对于简单的使用已毫不满足,最终为了一探究竟,翻开了JDK的源码,以下观点为自己的理解及看了多篇博客的总结,欢迎各位大神指出不对的地方,当然也欢迎和我一样刚学的同学,一起加油努力吧~~

TreeMap简述

TreeMap看名字就知道是Map家族的成员,通过key,value键值对的方式存储元素,相对于其他map,TreeMap是有序的,TreeMap的底层是由红黑树实现的,藉由LZ算法不精,红黑树相对也比较复杂,所以参考了很多资料,浅谈一下源码的剖析

TreeMap源码解析

在看源码之前,首先先大致说下树的概念。刚刚上面也说了TreeMap底层是由红黑树实现的,所以这里我们得对红黑树有个大致印象再看源码。树无非就是由根生枝生叶的一个结构,就像java里的类也是有根Object一层一层下来的,这就是普通的树,这里我们把根叫做根节点,这里有一个规则就是根节点数据会大于左子节点,小于右子节点,但是由于会一边倒,所以有了后期优化的平衡二叉树和红黑树等等,这里我们着重介绍下红黑树。

有了树的概念,下面来简单讲一下红黑树是什么,由于根节点为黑色,所以其子节点为红色,这个红色节点的子节点为黑色,依次延伸,红黑树会保持两边平衡,从任意一节点到叶子所有路径都会包含相同数量的黑色节点,红黑树会通过左旋右旋着色来重新进行结构的调整保持平衡。

简单介绍后,我们来深入一下源码

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

好了简单说一下,继承AbstractMap,实现了3个接口,第一个接口过会着重讲下,后两个接口应该都不陌生了,克隆方法与序列化,下面专门把那个比较陌生的接口拿出来看看到底是干什么的,NavigableMap这个接口继承了SortedMap,所以我们先看下SortedMap接口里定义了哪些方法

public interface SortedMap<K,V> extends Map<K,V> {
    /**
     * 根据key进行排序的方法
     */
    Comparator<? super K> comparator();

    /**
     * 将fromKey到toKey进行排序
     */
    SortedMap<K,V> subMap(K fromKey, K toKey);

    /**
     * 将小于toKey的key进行排序
     */
    SortedMap<K,V> headMap(K toKey);

    /**
     * 将大于fromKey的key进行排序
     */
    SortedMap<K,V> tailMap(K fromKey);

    /**
     * 返回第一个key值
     */
    K firstKey();

    /**
     * 返回最后一个key值
     */
    K lastKey();

    /**
     * 获得所有的key
     */
    Set<K> keySet();

    /**
     * 获得所有的value
     */
    Collection<V> values();

    /**
     * 将key,value转换为Set类型
     */
    Set<Map.Entry<K, V>> entrySet();
}

好了,看了上面的接口,我们来看下NavigableMap这个接口里新增的方法

public interface NavigableMap<K,V> extends SortedMap<K,V> {
    /**
     * 返回最大key值且key值小于参数key的entry
     */
    Map.Entry<K,V> lowerEntry(K key);

    /**
     * 返回最大key值且key值小于参数key
     */
    K lowerKey(K key);

    /**
     * 返回最大key值且key值 <= 参数key的entry
     */
    Map.Entry<K,V> floorEntry(K key);

    /**
     * 返回最大key值且key值 <= 参数key的key值
     */
    K floorKey(K key);

    /**
     * 返回最小key值且key值 >= 参数key的entry
     */
    Map.Entry<K,V> ceilingEntry(K key);

    /**
     * 返回最小key值且key值 >= 参数key的key值
     */
    K ceilingKey(K key);

    /**
     * 返回最小key值且key值 > 参数key的entry
     */
    Map.Entry<K,V> higherEntry(K key);

    /**
     * 返回最小key值且key值 > 参数key的key值
     */
    K higherKey(K key);

    /**
     * 返回最小key值的entry
     */
    Map.Entry<K,V> firstEntry();

    /**
     * 返回最大key值的entry
     */
    Map.Entry<K,V> lastEntry();

    /**
     * 移除最小key值的entry
     */
    Map.Entry<K,V> pollFirstEntry();

    /**
     * 移除最大key值的entry
     */
    Map.Entry<K,V> pollLastEntry();

    /**
     * 返回一个顺序反转后的Map
     */
    NavigableMap<K,V> descendingMap();

    /**
     * 返回包含所有key的set集合,正常排序
     */
    NavigableSet<K> navigableKeySet();

    /**
     * 返回包含所有key的set集合,顺序为反转排序
     */
    NavigableSet<K> descendingKeySet();

    /**
     * 返回key值在fromKey到toKey之间的Map
     */
    NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);

    /**
     * 返回key值 <= toKey的Map
     */
    NavigableMap<K,V> headMap(K toKey, boolean inclusive);

    /**
     * 返回key值 >= fromKey的Map
     */
    NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);

    /**
     * 等同于subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
     */
    SortedMap<K,V> subMap(K fromKey, K toKey);

    /**
     * 等同于headMap(K toKey, boolean inclusive)
     */
    SortedMap<K,V> headMap(K toKey);

    /**
     * 等同于tailMap(K fromKey, boolean inclusive)
     */
    SortedMap<K,V> tailMap(K fromKey);
}

看了一下接口的方法,感觉用处很相似,返回的都是key值排序后其中的key或entry,这边也没太多说的,知道每个方法干什么的就行,下面开始正式的看TreeMap的源码,首先我们来了解一下很重要的一个内部类

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

        /**
         * 创建新的entry,并赋予初始值
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        /**
         * 返回entry的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;
        }

        /**
         * 判断节点是否相等
         */ 
        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;
        }

        //toString打印方法
        public String toString() {
            return key + "=" + value;
        }
    }

上面的entry很重要,毕竟是由一个个节点构成的,所以我们要了解它的组成,下面看一下TreeMap的全局变量

    /**
     * 比较器,用来对key值进行比较排序,如为null,则采用默认的
     */
    private final Comparator<? super K> comparator;

    /**
     * 用来存放entry的
     */
    private transient Entry<K,V> root = null;

    /**
     * entry的数量
     */
    private transient int size = 0;

    /**
     * 修改次数
     */
    private transient int modCount = 0;

TreeMap的全局变量就这些,也都比较简单,comparator一个用来排序的,TreeMap之所以有序就是这个的作用了,其他的都比较简单,下面我们继续看构造函数

    /**
     * TreeMap无参构造,使用默认的比较
     */
    public TreeMap() {
        comparator = null;
    }

    /**
     * 参数为comparator,用给出的比较器进行排序
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    /**
     * 将整个map放入TreeMap中
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    /**
     * 创建一个新的TreeMap,包含传入参数map,排序方法相同
     */
    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) {
        }
    }

    /**
     * 将整个map放入TreeMap中
     */
    public void putAll(Map<? extends K, ? extends V> map) {
        //map大小
        int mapSize = map.size();
        //判断TreeMap无entry,传入参数元素不为0并且传入后map是SortedMap子类
        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;
            }
        }
        //调用父类putAll方法
        super.putAll(map);
    }

上面是TreeMap的几个构造方法,开篇说了由于红黑树相对比较复杂,所以这里我就挑一些常用的方法进行学习理解,如果有感兴趣的同学可以翻开源码深钻一下,应该可以收获很多,下面我们来看看一些常用的方法。

    /**
     * 返回键值对个数
     */
    public int size() {
        return size;
    }

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

    /**
     * 根据key获取value具体实现
     */
    final Entry<K,V> getEntry(Object key) {
        // 比较器存在时,调用getEntryUsingComparator方法
        if (comparator != null)
            return getEntryUsingComparator(key);
        //key为空,抛出异常    
        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时,p左子节点移动到p位置
            if (cmp < 0)
                p = p.left;
            //key大于当前节点key时,p右子节点移动到p位置
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

    /**
     * 比较器存在时,调用的方法
     */
    final Entry<K,V> getEntryUsingComparator(Object key) {
        K k = (K) key;
        Comparator<? super K> cpr = comparator;
        //比较器不为空时进入
        if (cpr != null) {
            //获取根节点
            Entry<K,V> p = root;
            //根节点不为空时进入
            while (p != null) {
                //比较两个key返回结果
                int cmp = cpr.compare(k, p.key);
                //结果小于0,p移动到左子节点
                if (cmp < 0)
                    p = p.left;
                //结果大于0,p移动到右子节点    
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

上面介绍了get的时候,TreeMap内部是如何去操作的,接下来看看put

    /**
     * key,value的形式向TreeMap中放值
     */
    public V put(K key, V value) {
        //根节点
        Entry<K,V> t = root;
        //根节点为空时进入
        if (t == null) {
            //比较key值,这里注释为类型检测
            compare(key, key); // type (and possibly null) check

            //新建节点,并修改size
            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;
                //将需要put的key与遍历出节点的key做比较
                cmp = cpr.compare(key, t.key);
                //当参数key大于节点key时,t的左子节点代替t节点
                if (cmp < 0)
                    t = t.left;
                //当参数key小于节点key时,t的右子节点代替t节点
                else if (cmp > 0)
                    t = t.right;
                else
                    //赋值并返回旧的value值
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            //参数key为空抛出异常
            if (key == null)
                throw new NullPointerException();
            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);
        }
        //创建新的entry
        Entry<K,V> e = new Entry<>(key, value, parent);
        //如新增节点key小于parent的key时,作为parent的左子节点插入,否则作为右子节点插入
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        //对整体的树形结构进行调整,保持平衡    
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }


    /** 调整整个树的整体结构 */
    private void fixAfterInsertion(Entry<K,V> x) {
        //新增节点颜色为红色
        x.color = RED;

        //循环,条件为x不为空,x不是父节点,x父节点颜色为红色,满足后跳出循环
        while (x != null && x != root && x.parent.color == RED) {
            /**
             * parentOf(x)父节点
             * leftOf(parentOf(parentOf(x)))父节点的父节点的左子节点
             * 相等时进入
             */
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                //获取父节点的父节点的右子节点
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                //如果当y节点为红色
                if (colorOf(y) == RED) {
                    //设置父节点颜色为黑色
                    setColor(parentOf(x), BLACK);
                    //设置y节点为黑色
                    setColor(y, BLACK);
                    //设置父节点的父节点的颜色为红色
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //当x为其父节点的右子节点
                    if (x == rightOf(parentOf(x))) {
                        //将x的父节点作为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) {
            //获取p的右子节点
            Entry<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设置为根节点,否则将r设为p的父节点的子节点
            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;
        }
    }

     /** 右旋方法 */
    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;
        }
    }

在这里将右旋方法贴出,与左旋类似就不详细说明了,相信大家应该都能看懂。到这里基本上最常用的方法基本已经介绍完了,对于算法不好的LZ表示头已经快绕晕了,下面列几个简单点也能用到的方法结束TreeMap的解析

    /**
     * 判断是否包含此key
     */
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

    /**
     * 判断是否包含此value
     */
    public boolean containsValue(Object value) {
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
            if (valEquals(value, e.value))
                return true;
        return false;
    }

    /**
     * 根据key值删除entry
     */ 
    public V remove(Object key) {
        //获取entry
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

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

    /**
     * 清空TreeMap
     */
    public void clear() {
        modCount++;
        size = 0;
        root = null;
    }

好了,没有太深入的了解TreeMap,但是主要分析了几个常用方法的实现原理,如果有感兴趣想深入的同学,可以自己看看源码,相信对算法有兴趣的朋友应该还是会喜欢的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值