TreeSet源码分析

Posted HelloWorld_EE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TreeSet源码分析相关的知识,希望对你有一定的参考价值。

TreeSet源码分析

功能:将Set中的元素按照一定的规则进行排序存储。

在其源码的内部实现中(如下),可以看到TreeSet时借助了TreeMap来实现的。

    public TreeSet() 
        this(new TreeMap<E,Object>());
    

    TreeSet(NavigableMap<E,Object> m) 
        this.m = m;
    

TreeSet.add方法

    public boolean add(E e) 
            //    private static final Object PRESENT = new Object();
        return m.put(e, PRESENT)==null;
    

从add方法中可以看到直接将e其作为key,value = new Object() 保存在了TreeMap中。

下面来看一下TreeMap类中put方法的内部实现哈

    public TreeMap() 
        comparator = null;
    

TreeMap.put(key,value)的实现思想比较简单:

1)就是实现了一个二叉排序树:要么是空树,要么满足以下条件:若左子树不空,则左子树所有结点的值均小于根结点的值,若右子树不空,右子树所有结点的值均大于根结点的值;左子树和右子树也是一颗二叉排序树。

2)具体为:从root节点开始遍历,利用比较器comparator来比较node.key与key的大小来确定此key应该存放的位置。注意:comparator可能为null,如果为null,则使用key的natural ordering(自然顺序),例如:没有指定comparator的String 类型key 的结果就是字典排序。

       public V put(K key, V value) 
        Entry<K,V> t = root;
        if (t == null) 
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        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);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
             while (t != 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;
     

TreeSet for-each方法的原理

经常我们会这里来遍历已经排好序的TreeSet

        for (String key : treeSet) 
           //...
        

那么,里面是如何来实现从最小的元素开始依次开始取元素的呢?

简单来说:由于存储是使用的二叉树排序的思想,因此对二叉树进行中序遍历即可得到有序的结果集。

从源码来分析,具体这就要看TreeMap中的keySet()方法的内部实现了

    public Set<K> keySet() 
        return navigableKeySet();
    

    public NavigableSet<K> navigableKeySet() 
        KeySet<K> nks = navigableKeySet;
        return (nks != null) ? nks : (navigableKeySet = new KeySet<>(this));
    

这里的KeySet这个类就是关键了。

    static final class KeySet<E> extends AbstractSet<E> implements NavigableSet<E> 
        private final NavigableMap<E, ?> m;
        KeySet(NavigableMap<E,?> map)  m = map; 

        public Iterator<E> iterator() 
            if (m instanceof TreeMap)
                return ((TreeMap<E,?>)m).keyIterator();
            else
                return ((TreeMap.NavigableSubMap<E,?>)m).keyIterator();
        
    //...省略了不需要关注的代码    
                   

从上面KeySet的iterator的实现中可以看到,通过如下的方法得到了类型为KeyIterator的迭代器。

        Iterator<K> keyIterator() 
            return new KeyIterator(getFirstEntry());
        

        /**
         * Returns the first Entry in the TreeMap (according to the TreeMap's
         * key-sort function).  Returns null if the TreeMap is empty.
         */
        final Entry<K,V> getFirstEntry() 
            Entry<K,V> p = root;
            if (p != null)
                while (p.left != null)
                    p = p.left;
            return p;
         

getFirstEntry函数的功能:找到排序结果中最小的那个元素。

下面主要看下 KeyIterator 这个类

这个类的next方法就是用来获取TreeSet中的下一个排好序的元素的。

具体实现思路为:由于上面讲解的put方法中我们知道是根据“左根右”的思想来存储的[最小值,中间值,最大值],因此这里的获取最小值的思路也是如此。

     final class KeyIterator extends PrivateEntryIterator<K> 
        KeyIterator(Entry<K,V> first) 
            super(first);
        
        public K next() 
            return nextEntry().key;
        
    

        final Entry<K,V> nextEntry() 
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            next = successor(e);
            lastReturned = e;
            return e;
                          

    /**
     * Returns the successor of the specified Entry, or null if no such.
     */
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) 
        if (t == null)
            return null;
        else if (t.right != null) //判断t节点的右边还有没有元素,如果有取出最小的。
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
         else //如果此时t的右边没有元素可遍历了,则遍历t的父节点的父节点
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) 
                ch = p;
                p = p.parent;
            
            return p;
        
    

小结

比较简单哈,就是根据顺序存储,然后按照一定的顺序取出。

以上是关于TreeSet源码分析的主要内容,如果未能解决你的问题,请参考以下文章

死磕 java集合之TreeSet源码分析

集合之TreeSet(含JDK1.8源码分析)

TreeSet 源码分析

源码分析TreeMap和TreeSet

TreeSet 源码分析

#yyds干货盘点# Map - TreeSet & TreeMap 源码解析