之前说过,collection集合下分为两类集合,一类是list类,一类是set类集合,两种集合各有各自的特点:
list集合:有序,可重复,有索引
set集合:无序,不可重复,无索引
我们知道list集合中的Arrylist集合底层就是用数组实现的,固然有索引,那么为什么set集合就没有索引呢?
让我们先来了解set集合的底层原理:
哈希表 = 数据+链表(JDK8之前)
哈希表 = 数组+链表+红黑树(JDK8之后)
注意:这里的数组和Arrylist底层一样,都是动态数组,当有75%的位置存入数据时,数组会自动扩容。
这里引入了哈希表的概念,让我们先来了解一下哈希值:哈希值是一个十进制的整数,有系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来的地址,不是数据实际存储的物理地址)。
无序、无索引的原因:当集合中存入数据时,底层会对数据的哈希值进行求余,求出的值即为数组的下标,然后底层将数据存在数组的这个下标处。因为每个数据的哈希值不同,所以求出的数组下标是无序杂乱的,故无索引。
不重复的原因:当数据的哈希值重复(哈希碰撞)时,底层将会以链表的形式将新传入的数据挂在老数据下方,同时底层设置了equals方法,当判断存入的数据和之前某个存入的数据相同时,不会存入,故不重复。
那么,这里肯定会有大聪明担心了:如果链表的长度过长,查找数据的效率还是会变慢呀?
对,所以设计者也想到了,在JDK8之后,如果数组场长度大于等于64,且某个链表长度大于8时,底层自动将链表转换为红黑树,方便查找。
所以:set集合是一种增删改查性能都较好的数据结构。
了解完了set集合的底层原理,我们来了解一下常见的三种set集合:
HashSet、LinkedHashSet、TreeSet:
public static void main(String[] args) {
//1.创建一个set集合的对象
Set<Integer> set = new HashSet<>();//无序不重复无索引
set.add(666);
set.add(555);
set.add(555);
set.add(777);
set.add(888);
set.add(888);
set.add(666);
System.out.println(set);
Set<Integer> set2 = new LinkedHashSet<>();//有序不重复无索引
set2.add(666);
set2.add(555);
set2.add(777);
set2.add(888);
set2.add(888);
set2.add(666);
System.out.println(set2);
Set<Integer> set3 = new TreeSet<>();//有序不重复无索引,默认升序排序
set3.add(666);
set3.add(555);
set3.add(777);
set3.add(888);
set3.add(888);
set3.add(666);
System.out.println(set3);
}
欸,这个TreeSet看着有点熟悉哦,没错,只有在基本数据类型时他才能默认排序,当出现定义数据类型的时候,我们需要在类中重写CompareTo方法来规定排序规则
例:我们这里定义一个学生类
public class Student implements Comparable<Student> {
@Override
public int compareTo(Student o) {
//左>右 1
//左=右 0
//左<右 -1
return this.age - o.age;
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public Student() {
}
private String name;
private int age;
private double height;
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
重写了compareTo方法之后,就可以进行比较辣!
那么,除此之外,还有第二种比较方法,就是调用treeSet集合自带的有参构造器,可以设置Comparator对象。
public static void main(String[] args) {
Set<Integer> tree = new TreeSet<>();
//比较定义类方式一:让定义类实现comparable接口,重写里面的compareTo方法
//方式二:通过treeSet集合自带的有参构造器,可以设置Comparator对象(比较器对象,属于指定比较规则)
Set<Student> students = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
});
//如果同时重写方法和调用treeSet比较器对象,则treeSet会优先执行比较器对象,
// 不会执行重写的compareTo方法
students.add(new Student("张三",23,161.1));
students.add(new Student("李四",21,159.8));
students.add(new Student("王五",25,168.8));
students.add(new Student("赵六",19,171.9));
students.add(new Student("王五",25,168.8));
System.out.println(students);
//这里执行时发现王五丢了,原因是王五中的比较数据都相等,treeSet不重复,直接抹掉一个
}
欸,靓仔你应该发现了吧,这是啥,匿名内部类啊!再点进去看源码,是函数式接口,好,直接搬出我们的lambda表达式!
public static void main(String[] args) {
Set<Integer> tree = new TreeSet<>();
tree.add(1);
tree.add(4);
tree.add(5);
tree.add(3);
System.out.println(tree);//有序,不重复,无索引 底层:红黑树
//注意:treeSet中对于基本类型的Interage、Double,按默认值本身大小可以做到升序排序
//对于字符串类型:默认按照首字母的编号升序排序
// 但是对于自定义的类型如Student类是不能直接默认进行排序的
//比较定义类方式一:让定义类实现comparable接口,重写里面的compareTo方法
//方式二:通过treeSet集合自带的有参构造器,可以设置Comparator对象(比较器对象,属于指定比较规则)
// Set<Student> students = new TreeSet<>(new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// return Double.compare(o1.getHeight(), o2.getHeight());
// }
// });
//化简后:
Set<Student> students = new TreeSet<>( (o1, o2)-> Double.compare(o1.getHeight(), o2.getHeight()));
students.add(new Student("张三",23,161.1));
students.add(new Student("李四",21,159.8));
students.add(new Student("王五",25,168.8));
students.add(new Student("赵六",19,171.9));
students.add(new Student("王五",25,168.8));
System.out.println(students);
//这里执行时发现王五丢了,原因是王五中的比较数据都相等,treeSet不重复,直接抹掉一个
}
好了,到这里差不多就是set集合的所有内容了!
但是,我们没有一刻的停留,接下来赶到战场的是——Map集合!