TreeSet 几个特征 及其原理。参照源码 分析其底层实现

TreeSet几个特征

1.无序:放入集合中的顺序和从集合中取出的顺序不相同

2.传入的值不能重复:放入集合中的值不能重复,重复则覆盖。

3.传入的值不能为null:不可以想TreeSet集合中传入null,否则编译抛出NullPointerException异常。

4.排序:从集合中取出时必须按照其自然顺序排序,要求使用该集合的对象的类必须实现Compare或者在创建TreeSet时  在构造方法中传入 一个比较器接口(Comparator)的子类对象或者 匿名内部类。

先上代码举例:

自己创建的Student类  实现了Comparable接口 

public class Student implements Comparable<Student>{

	private String name;
	
	
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Student(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}


//重写了compareTo方法
	@Override
	public int compareTo(Student o) {
		// TODO Auto-generated method stub
		int res = name.compareTo(o.name);
		return res;
	}
}

测试类TestCollection

public class TestCollection {

	static Student[] stu;

	public static void main(String[] args) {

		
		testTreeSet();
	}


/**
	 * TreeSet: 底层使用 TreeMap的树形结构 该类 要求使用TreeSet的对象的类 去实现Comparable<T>接口 特征: 无序 不可重复
	 * 值不能为null 按照自然顺序输出 或者根据创建时映射的Comparator排序
	 */
	private static void testTreeSet() {
		Student stu[] = initStu();
		// TreeSet treeSet = new TreeSet();
		/**
		 * 如果Student对象没有实现Comparable接口,
		 * 那么在TreeSet的构造方法中传入 实现了 接口Comparator 的对象 或者匿名内部类
		 */
		TreeSet<Student> treeSet = new TreeSet<Student>(new Comparator<Student>() {

			@Override
			public int compare(Student o1, Student o2) {
				// TODO Auto-generated method stub
				return o1.getName().compareTo(o2.getName());
			}

		});

		for (int i = 0; i < stu.length; i++) {
			treeSet.add(stu[i]);
		}
		System.out.println("********************TreeSet迭代器输出测试****************");
		Iterator<Student> dit = treeSet.iterator();
		while (dit.hasNext()) {
			System.out.println(dit.next());
		}
		System.out.println("****************************************************** \n");

	}

	private static Student[] initStu() {

		Student s1 = new Student("zs");
		Student s2 = new Student("ls");
		Student s3 = new Student("zmj");
		Student s4 = new Student("zh");
		Student s5 = new Student("hst");
		Student[] stu = { s1, s2, s3, s4, s5, s5 };
		return stu;
	}

}

输出结果:

********************TreeSet迭代器输出测试****************
Student [name=hst]
Student [name=ls]
Student [name=zh]
Student [name=zmj]
Student [name=zs]
****************************************************** 

如果传入的Student对象中有null  则:

Exception in thread "main" java.lang.NullPointerException
	at Test412HashMap.TestCollection$1.compare(TestCollection.java:80)
	at Test412HashMap.TestCollection$1.compare(TestCollection.java:1)
	at java.util.TreeMap.put(Unknown Source)
	at java.util.TreeSet.add(Unknown Source)
	at Test412HashMap.TestCollection.testTreeSet(TestCollection.java:86)
	at Test412HashMap.TestCollection.main(TestCollection.java:25)

 为什么传入null值回报出 java.lang.NullPointerException 呢?

深入源码去瞅瞅。

通过查看TreeSet的add方法,我们发现它的底层是通过一个Map集合的put方法实现的。

先来看一下add()方法上面的注释

238行

Adds the specified element to this set if it is not already present.

如果指定的元素尚未出现,则将其添加到此集合。

242~243 行

If this set already contains the element, the call leaves the set unchanged and returns

如果这个集合已经包含元素,那么调用将保持集合*不变并返回

PS:虽然还未继续看源代码 但是这两行注释已经明确的告诉开发者 如果向TreeSet集合存入重复元素,则保持集合不变并返回。对 本文中的特征2 不能重复进行说明

246 247两行的 返回值说明 也清楚地说到 如果这个集合还没有包含指定元素 则返回true;

再看248 249这两行抛出的第一个异常@throws ClassCastException 

@throws ClassCastException if the specified object cannot be compared  with the elements currently in this set

如果指定的对象不能与当前集合中的元素进行比较,则抛出ClassCastException异常

PS:这些注释也就是 对本文的特征4 排序做的解释和说明。

最后来看250~252行抛出的另一个异常@throws NullPointerException

 @throws NullPointerException if the specified element is null and this set uses natural ordering, or its comparator does not permit null elements

如果指定的元素为null,并且该集合使用自然顺序,或者其比较器不允许空元素,则抛出NullPointerException

PS:对本文中特征3 不能传入null 做了解释说明.

看完注释,言归正传继续深入源码。(截取了一部分可能我们会用到的代码)

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    /**
     * The backing map.
     */
    private transient NavigableMap<E,Object> m;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a set backed by the specified navigable map.
     */
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    /**
     * Constructs a new, empty tree set, sorted according to the
     * natural ordering of its elements.  All elements inserted into
     * the set must implement the {@link Comparable} interface.
     * Furthermore, all such elements must be <i>mutually
     * comparable</i>: {@code e1.compareTo(e2)} must not throw a
     * {@code ClassCastException} for any elements {@code e1} and
     * {@code e2} in the set.  If the user attempts to add an element
     * to the set that violates this constraint (for example, the user
     * attempts to add a string element to a set whose elements are
     * integers), the {@code add} call will throw a
     * {@code ClassCastException}.
     */
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
   /**
     * Constructs a new, empty tree set, sorted according to the specified
     * comparator.  All elements inserted into the set must be <i>mutually
     * comparable</i> by the specified comparator: {@code comparator.compare(e1,
     * e2)} must not throw a {@code ClassCastException} for any elements
     * {@code e1} and {@code e2} in the set.  If the user attempts to add
     * an element to the set that violates this constraint, the
     * {@code add} call will throw a {@code ClassCastException}.
     *
     * @param comparator the comparator that will be used to order this set.
     *        If {@code null}, the {@linkplain Comparable natural
     *        ordering} of the elements will be used.
     */
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }


   /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element {@code e} to this set if
     * the set contains no element {@code e2} such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns {@code false}.
     *
     * @param e element to be added to this set
     * @return {@code true} if this set did not already contain the specified
     *         element
     * @throws ClassCastException if the specified object cannot be compared
     *         with the elements currently in this set
     * @throws NullPointerException if the specified element is null
     *         and this set uses natural ordering, or its comparator
     *         does not permit null elements
     */
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

}

然后跟着m   找到了private transient NavigableMap<E,Object> m;

点进去发现  NavigableMap是个接口。。。。线索仿佛中断了。。但是在我eclipse的控制台上面 停留着当时  传入Null值时报的异常。

TreeMap.put。

查阅AIP文档  TreeMap

果然实现了NavigableMap接口。而且排序方法也和TreeSet的一脉相承。

转移战场 TreeMap

因此具体类是TreeMap类,因此我们要看add方法的源码就需要去找TreeMap的put方法的源码即可。

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


    private final Comparator<? super K> comparator;

    private transient Entry<K,V> root;

  
    private transient int size = 0;

   private transient int modCount = 0;

/*构造一个新的、空的树映射,该映射根据给定比较器进行排序。插入该映射的所有键都必须由给定比较器进行相互比较:对于映射中的任意两个键 k1 和 k2,执行 comparator.compare(k1, k2) 都不得抛出 ClassCastException。如果用户试图将违反此约束的键放入映射中,则 put(Object key, Object value) 调用将抛出 ClassCastException。 

参数:
comparator - 将用来对此映射进行排序的比较器。如果该参数为 null,则将使用键的自然顺序。*/
   
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }



/*构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序 进行排序。插入此新映射的所有键都必须实现 Comparable 接口。另外,所有这些键都必须是可互相比较的:对于映射中的任意两个键 k1 和 k2,执行 k1.compareTo(k2) 都不得抛出 ClassCastException。此方法的运行时间为 n*log(n)。 

参数:
m - 其映射关系将存放在此映射中的映射 
抛出: 
ClassCastException - 如果 m 中的键不是 Comparable,或者是不可相互比较的 
NullPointerException - 如果指定映射为 null */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }


/*构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。此方法是以线性时间运行的。 

参数:
m - 有序映射,其映射关系将存放在此映射中,并且其比较器将用来对此映射进行排序 
抛出: 
NullPointerException - 如果指定映射为 null */
    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<K,V> 中的 put
覆盖:
类 AbstractMap<K,V> 中的 put
参数:
key - 要与指定值关联的键
value - 要与指定键关联的值 
返回:
与 key 关联的先前值;如果没有针对 key 的映射关系,则返回 null。(返回 null 还可能表示该映射以前将 null 与 key 关联。) 
抛出: 
ClassCastException - 如果指定键不能与映射中的当前键进行比较 
NullPointerException - 如果指定键为 null 并且此映射使用自然顺序,或者其比较器不允许使用 null 键
*/

 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;
    }
}

看到这里 源码看的不是很明白,大致意思 参照一篇文章:

通过源码我们可以发现,存入元素的时候,它创建了一个树,第一个元素就是树的根节点,后面的元素依次从树的根节点开始向后比较(创建比较器,利用comparator()方法进行比较),小的就往左边放,大的就往右边放,而相同的就不放进去(实现了唯一性)。取出元素的时候,它采用先序遍历的方法(左 中 右)遍历整个树,达到有序。
 

参考博客:https://blog.csdn.net/qq_36748278/article/details/77915801#commentsedit

希望大佬们有想法一起参与讨论。

猜你喜欢

转载自blog.csdn.net/Hurricane_m/article/details/89253343