前言
研究完了,HashSet和LinkHashSet,那么TreeSet当然也是不能错过的了。这章我们就一起研究下TreeSet的源码
正文
类的描述
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
TreeMap 是基于NavigableSet实现的。元素使用其自然顺序排序,或由 Comparator在设定的创建时间进行排序,这两种方式取决于使用哪个构造函数。
此实现为基本操作(添加、删除和包含)提供有保证的 log(n) 时间成本。
请注意,如果要正确实现 Set 接口,则集合维护的排序(无论是否提供显式比较器)必须与 equals 一致。 (请参阅 Comparable 或 Comparator 以获取与 equals 一致的精确定义。)这是因为 Set 接口是根据 equals 操作定义的,但是 TreeSet实际是使用它的 compareTo(或 compare)方法执行所有元素比较,所以两个 被此方法视为相等的元素,从集合的角度来看,是相等的。 集合的行为是明确定义的,即使它的顺序与 equals 不一致; 它只是不遵守 Set 接口的一般约定。
请注意,此实现不是同步的。 如果多个线程同时访问一个树集,并且至少有一个线程修改了该集,则必须在外部进行同步。 这通常是通过同步一些自然封装集合的对象来完成的。 如果不存在此类对象,则应使用 Collections.synchronizedSortedSet 方法“包装”该集合。 这最好在创建时完成,以防止对集合的意外不同步访问
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
此类的迭代器方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时间修改集合,除了通过迭代器自己的 remove 方法以外的任何方式,迭代器都会抛出 ConcurrentModificationException。 因此,面对并发修改,迭代器快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。
请注意,无法保证迭代器的快速失败行为是实时的,因为一般而言,在存在非同步并发修改的情况下不可能做出任何硬保证。 快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。 因此,编写一个依赖此异常来确保其正确性的程序是错误的:迭代器的快速失败行为应该仅用于检测错误。
构造方法
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
通过上面的构造方法我们可以得知,TreeSet是通过TreeMap来实现的,然后大致分为指定排序方法的构造方法,和元素自然排序的构造方法,TreeMap我们在接下来的Map模块进行研究
添加
//
public boolean add(E e) {
return m.put(e, PRESENT)==null;
// == null的问题请参考HashSet
}
-- 使用的是TreeMap的put方法
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); -- 如果旧的,会替换value, 并返回旧的value
} while (t != null);
}
else {
if (key == null) -- 如果key为空会报NullPointerException
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); --如果旧的,会替换value, 并返回旧的value
} while (t != null);
}
-- 如果是新节点
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
-- 插入元素后操作树,放到HashMap中讲
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
// 返回旧的value
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean addAll(Collection<? extends E> c) {
// Use linear-time version if applicable
if (m.size()==0 && c.size() > 0 && //当当前集合为空,且C是有序集合
c instanceof SortedSet &&
m instanceof TreeMap) {
SortedSet<? extends E> set = (SortedSet<? extends E>) c;
TreeMap<E,Object> map = (TreeMap<E, Object>) m;
Comparator<?> cc = set.comparator();
Comparator<? super E> mc = map.comparator();
// 如果当前TreeMap排序和集合C的排序方法是一致的
if (cc==mc || (cc != null && cc.equals(mc))) {
map.addAllForTreeSet(set, PRESENT);
return true;
}
}
return super.addAll(c);
}
/** Intended to be called only from TreeSet.addAll */
// 这个我向下继续看了但是没有看懂,我准备在TreeMap中研究, 如果有知道的同学,可以在下方留言告诉
//我,万分感谢!
void addAllForTreeSet(SortedSet<? extends K> set, V defaultVal) {
try {
buildFromSorted(set.size(), set.iterator(), null, defaultVal);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
-- super,addAll(c) 调用的是 AbstractConllection的addAll
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e)) -- 这里用的依然是TreeSet的add
modified = true;
return modified;
}
通过添加我们可以看出TreeSet是通过TreeMap的put来实现的,是通过TreeMap的key来实现不可重复的。
TreeMap的value和HashSet一样都是指定了一个变量PRESENT
通过put的返回值 == null 判断集合元素是否重复,因为如果TreeMap的key存在的话,会返回旧值,而TreeSet的默认value都是PRESENT,所以如果元素重复,put会返回PRESENT ,如果不重复会返回null
当未指定比较方法时,key==null时会报空指针异常,其他使用compare方法时,如果key为空,一般也会报空指针异常,所以得到结论时TreeSet的key是不可以为null的
addAll方法中特殊逻辑,在放到TreeMap中研究,如果有知道的同学,请下方留言告诉我,让我可以有参考的去研究TreeMap,然后进行验证。
移除
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
//调用了map remove
public V remove(Object key) {
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p); //TreeMap删除元素的操作
return oldValue;
}
移除是调用TreeMap的remove方法来实现的
结论
其他的方法我就不在这里写,有兴趣的同学可以自己看下
TreeSet是基于TreeMap实现的,通过TreeMap的key不可重复,和key 不能为空来实现TreeSet的两个特性。
TreeSet基本所有的方法都是通过TreeMap的方法来实现的
TreeSet不是线程安全的