文中源码均基于JDK1.8进行分析。
2.2 TreeSet
2.2.1 TreeSet底层实现和构造函数
package java.util;
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
{
private transient NavigableMap<E,Object> m;// 使用 NavigableMap 的 key 来保存 Set 集合的元素
private static final Object PRESENT = new Object(); // 使用一个 PRESENT 作为 Map 集合的所有 value
// 构造器以指定的 NavigableMap 对象创建 Set 集合
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);
}
public Iterator<E> iterator() {
return m.navigableKeySet().iterator();
}
public Iterator<E> descendingIterator() {
return m.descendingKeySet().iterator();
}
public NavigableSet<E> descendingSet() {
return new TreeSet<>(m.descendingMap());
}
public int size() {
return m.size();
}
public boolean isEmpty() {
return m.isEmpty();
}
public boolean contains(Object o) {
return m.containsKey(o);
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
public void clear() {
m.clear();
}
}
从TreeSet源码中我们可以看到,TreeSet的底层是TreeMap,例如添加元素就是调用TreeMap的put方法,添加的数据存入了map的key的位置,而value则固定是PRESENT。TreeSet中的元素是有序且不重复的,因为TreeMap中的key是有序且不重复的。
关于TreeMap的实现原理将在后期博客中详细介绍!!
2.2.2 TreeSet存储原理
(1)元素如何存储进去?
TreeSet底层的数据结构是红黑树(是一个自平衡的二叉树),存储第一个元素时,直接作为根节点存储;存储第二个元素时,元素与根节点进行比较,如果比根小,看左子树是否有元素,如果为null,直接存储;否则,以左子树为根继续比较,如果比根大,看右子树是否有元素,如果为null,直接存储,否则,以右子树为根继续比较,如果找到相等的,则确定唯一性。
(2)元素如何取出?
可以通过前序遍历、中序遍历和后序遍历取出元素。
(3)TreeSet底层是如何保证元素的排序和唯一性的?
根据源码,我们知道,TreeSet的底层代码可以通过两种方式来实现其元素的有序性,分别是自然排序和比较器排序,根据比较的返回值是否是0来决定是否唯一。
- 自然排序(元素具备比较性)
compareTo()这个方法是定义在 Comparable里面的,所以你要想重写该方法,就必须是先实现Comparable接口,这个接口表示的就是自然排序。因此让元素所属的类实现Comparable接口,重写其中的compareTo方法是第一种方式。
例如:自己写了一个类如下
public class Student implements Comparable{
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Object o) {
//定义比较规则:根据对象的age进行升序排列
//判断后返回1 -1 0
/**1:大于
* -1:小于
* 0:等于
*/
//1.判断对象o是否为空
if(o==null){
return 0;
}
//判断对象是否属于同一类型
if(o instanceof Student){
//强制类型转换
Student stu=(Student) o;
//根据对象的age进行判断
if(stu.getAge()>age){
return 1;
}else if(stu.getAge()<age){
return -1;
}else{
return 0;
}
}
return 0;
}
}
测试
Set set=new TreeSet();
/*set.add("5");
set.add("2");
set.add("8");
set.add("3");
set.add("6");
set.add("4");*/
set.add(new Student(1, "zhangsan1", 16));
set.add(new Student(8, "zhangsan8", 20));
set.add(new Student(2, "zhangsan2", 32));
set.add(new Student(6, "zhangsan6", 19));
set.add(new Student(4, "zhangsan4", 18));
for (Object o : set) {
System.out.println(o);
}
结果
- 比较器排序(集合具备比较性)
让集合构造方法接收Comparator的实现类对象,在实现类中重写compare()方法。这种方法通过调用集合的带参构造来实现比较,例如下面源码中的带参构造器,在addAll方法中用到了。
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public Comparator<? super E> comparator() {
return m.comparator();
}
public boolean addAll(Collection<? extends E> c) {
// Use linear-time version if applicable
if (m.size()==0 && c.size() > 0 &&
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();
if (cc==mc || (cc != null && cc.equals(mc))) {
map.addAllForTreeSet(set, PRESENT);
return true;
}
}
return super.addAll(c);
}
注意的是:根据对源码和底层数据结构红黑树的理解,无论是通过自然排序还是通过比较器排序,数据结构核心是通过将根节点和子节点比较来进行。
2.2.3 HashSet和TreeSet的比较
(1)底层存储的数据结构不同
HashSet:底层使用哈希表结构存储,元素允许为空;
TreeSet:使用树结构进行存储,不可重复,不能存放null数据。
(2)存储时保证数据唯一性依据不同
HashSet是通过重写hashCode()方法和equals()方法来保证;
TreeSet是通过Comparable接口的compareTo()方法来保证的。
(3)有序性不同
HashSet元素无序;
TreeSet是排序后的Set集合。