回顾
上篇文章Java基础——单列集合(一)主要讲了单列集合中的List系列集合,这篇文章紧接着来介绍Set系列集合,让我们先来回顾一下集合体系:
再来看看不同集合的底层结构:
Set集合概述
HashSet
概念
- HashSet集合底层采用哈希表存储数据
- 哈希表是一种对于增删改查数据性能都较好的结构
哈希表组成:
JDK8之前:数组+链表
JDK8开始:数组+链表+红黑树
链表和红黑树都是为了解决哈希冲突而存在的
哈希值
如果没有hashCode重写,则利用地址值计算哈希值
底层原理
JDK8以前
JDK8以后
- 当数组存储数据的长度达到(默认长度*加载因),数组就会扩容到原来的二倍
- 当链表长度大于8而且数组长度大于等于64时,链表自动转换为红黑树
HashSet组成:数组+链表+红黑树
三个特点
1、HashSet的无序:存和取的顺序不一样
哈希值是随机的,则存入顺序是随机的
2、HashSet没有索引
因为有链表和红黑树的存在(无索引)
3、HashSet不重复
利用hashCode方法和eqauls方法保证数据去重
代码实现
先创建Student1类,并在其中重写equals和hashCode方法
public class Student1 {
private String name;
private int age;
public Student1() {
}
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student1 student1 = (Student1) o;
return age == student1.age && Objects.equals(name, student1.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
在测试类中将Student1类的对象添加进HashSet集合中
public class HashSet {
public static void main(String[] args) {
Student1 s1 = new Student1("zhangsan",23);
Student1 s2 = new Student1("wangwu",24);
Student1 s3 = new Student1("lisi",25);
Student1 s4 = new Student1("zhangsan",23);
java.util.HashSet<Student1> hs = new java.util.HashSet<>();
System.out.println(hs.add(s1));
System.out.println(hs.add(s2));
System.out.println(hs.add(s3));
System.out.println(hs.add(s4));//去重,无法添加
System.out.println(hs);//存储顺序和添加顺序不一样
}
}
1、由于自动去重,所以s4无法添加
2、 由于存取顺序不一样,打印出来的顺序不是按照添加顺序:
LinkedHashSet
底层原理
如果只要求数据去重,默认使用HashSet
如果要求去重且存取有序,使用LinkedHashSet
TreeSet
特点
- 不重复,无索引,可排序
- 可排序:按照元素的默认规则(从小到大)排序(自动排序)
- TreeSet集合底层基于红黑树的数据结构实现排序的,增删改查性能都较好
默认规则
TreeSet的两种比较方式
一、默认排序:Javabean类实现Comparable接口指定比较规则
TreeSet底层是红黑树而不是哈希表,所以不用重写hashCode和eqauls方法
TreeSet要在类(集合中存储的数据类型如果是类)中实现Comparable接口,并重写compareTo方法
二、比较器排序:创建TreeSet对象时候,传递比较器Comparator指定规则
TreeSet构造方法中,允许Comparator接口作为参数,使其中的comparator方法作为排序规则
使用匿名内部类的方式,创建Comparator接口实现类对象并重写compare方法
string类中已重写过compareTo方法(按照字典序排序)
如何选择
默认情况下用第一种排序方式,如果第一种不能满足则使用第二种
如果两种方式同时存在,以第二种为准
补充
compare和compareTo方法的区别:
所属的接口/类:
compareTo
方法是Comparable
接口的一部分。当你想要一个类的对象能够相互比较时,你通常会让这个类实现这个接口。这个方法是由类的实例(对象)调用的。compare
方法是Comparator
接口的一部分。当你想要一个其它类(不是待比较对象的类)来定义如何比较对象时,你会实现这个接口。这个方法是由Comparator
的实例调用的。使用场景:
compareTo
:当你想要类本身定义其自然排序顺序时,你会在类中实现Comparable
接口,并提供compareTo
方法的实现。例如,String
类实现了Comparable<String>
接口,允许你直接使用字符串进行比较和排序。compare
:当你不想在类本身中定义排序顺序时,你会创建一个实现Comparator
接口的类,并提供compare
方法的实现。方法签名:
compareTo
的典型签名是int compareTo(T o)
,其中T
是类自身的类型,并且方法返回一个整数,表示当前对象与参数对象之间的比较结果。compare
的典型签名是int compare(T o1, T o2)
,其中T
是要比较的对象的类型,并且方法返回一个整数,表示第一个参数对象与第二个参数对象之间的比较结果。调用方式:
- 对于
compareTo
,你通常会让A类实现comparable接口并重写compareTo方法,假设a、b都是A类的对象,这样调用它:a.compareTo(b)。- 对于
compare
,你通常会这样调用它(假设你有一个Comparator
的实例叫做c):c.compare(object1, object2)。