一、Set接口介绍
set是一个散列集合
Set特点:不包含重复元素, 如果有多个重复元素,只会显示一个。
常用子接口:SortedSet<E>
常用子类:HashSet LinkedHashSet TreeSet
二、HashSet类
(1)HashSet底层数据的数据结构是HashMap,而HashMap的底层是数组+链表的结构。
特点:元素不能重复,元素的位置不是一成不变的,是无序的,线程不安全的。
(2)HashMap底层存入数据原理:
【1】数据存入HashMap前先进行哈希算法,得到的值,在进行哈希算法(二次哈希),最后得到的结果,在0~15之间(默认的情况下)。
【2】HashMap中每一个数组的位置称之为桶(bucket),数据存入桶之前,会先和桶中的数据进行比较,如果数据相同,那么就舍弃数据,如果数据不同,那么在存入链表中。并且链表是栈结构的(先进后出)。意味着,先进来的在栈尾,后进来的在栈顶。位置改变,所以称之为无序的。
【3】HashMap每次扩容是原来容量的一倍。扩容时会重新进行二次计算,重新排列位置。这个过程称之为rehash。
综上所述:元素位置改变有两点:一是链表的栈结构(先进后出),二是扩容时,进行rehash。
(3)HashSet的初始容量为16,默认加载因子为0.75。当然也可以指定初始容量,与加载因子。
注意如下几点:
如果指定的初始容量过小,会频繁扩容,频繁的计算,效率降低。
如果指定的初始容量过大,会浪费内存。
如果加载因子过小,会频繁扩容。频繁的rehash,就会影响效率
如果加载因子过大,链表的长度会很长,查询速率就降低。
补充:
【1】HashSet指定初始容量,会自动修改为2的n次方的形式。
10 --> 16
15 -->16
7-->8
【2】在JDK1.8后,如果桶上的链表长度大于8,那么会将链表结构扭转为红黑二叉树结构。从而提升效率。
三、LinkedHashSet类
通过翻看LinkedHashSet的源码,我发现,LinkedHashSet中的构造方法,都是调用父类的,而这个父类就是HashSet。所以,它
的初始容量,加载因子扩容方式都是和HashSet一样的。唯一不同的是,它是一个双向链表,其中有一条链表,专门用来控制顺序。
(1)LinkedHashSet是一个有序的集合:存储和取出的顺序相同。因为,它是一个双向链表,专门有一条用来记录顺序。
(2)底层使用数组加链表和一个记录顺序的链表、线程不安全的集合。
(3)默认初始容量为16,默认加载因子为0.75。
四、TreeSet类
TreeSet中如果存放的是字符串,会自动进行一个升序排序。因为字符串中实现了Comparable接口,并重写了compareTo方法。
我们可以通过自定义规则,让TreeSet来帮我们进行排序。
如果我们要使用TreeSet进行排序,那么我们的自定义类需要实现Comparable接口,并重写compareTo( )方法。
例:创建学生类并根据学生年龄进行排序
public class TreeDemo {
public static void main(String[] args) {
Student stu = new Student("张三", 18);
Student stu1 = new Student("李四", 12);
Student stu2 = new Student("王五", 20);
Set<Student> set = new TreeSet<Student>();
set.add(stu);
set.add(stu1);
set.add(stu2);
for (Student student : set) {
System.out.println(student);
}
}
}
class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
结果:升序排序
Student [name=李四, age=12]
Student [name=张三, age=18]
Student [name=王五, age=20]
实现原理:
compareTo返回值是int类型。
如果返回的是负数,那么表示本对象小于传入的对象,是升序。
如果返回的是正数,那么表示本对象大于传入的对象,是降序。
所以,可以通过交换本对象与传入对象的位置,进行升,降设置