Set系列集合
1、无序:存取顺序不一致
2、不重复:可以去除重复
3、无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
Set集合的实现类
1,HashSet:无序、不重复、无索引
2、LinkedHashSet:有序、不重复、无索引
3、TreeSet:可排序、不重复、无索引
Set接口中的方法上基本上与Collection的API一致。
//创建Set实现类集合的对象
Set<String> s=new HashSet<>();
s.add("张三");
boolean r2=s.add("张三");
System.out.println(r2);
System.out.println(s);
//false
//不能重复
//[张三]
s.add("李四");
s.add("王五");
//[李四, 张三, 王五]
//存入和输出的顺序不一定相同
//迭代器遍历
Iterator<String> it=s.iterator();
while(it.hasNext()){
String cd=it.next();
System.out.println(cd);
}
//增强for遍历
for (String s1 : s) {
System.out.println(s);
}
//匿名内部类
s.forEach(new Consumer<String>() {
@Override
public void accept(String str) {
System.out.println(str);
}
});
//简化Lambda表达式
s.forEach(str-> System.out.println(str));
HashSet底层原理
1、HashSet集合底层采取哈希表存储数据
2、哈希表是一种对于增删改查数据性能都较好的结构
哈希表组成
1、JDK8之前:数组+链表
2、JDK8开始:数组+链表+红黑树
哈希值:对象的整数表现形式
1、根据hashCode方法算出来的int类型的整数
2、该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
3、一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
对象的哈希值的特点
1、如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
2、如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
3、在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
//创建学生类
public class Student {
private String name;
private int age;
//重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
//创建Test类
public class Test {
public static void main(String[] args) {
//创建对象
Student stu1=new Student("zhangsan",23);
Student stu2=new Student("zhangsan",23);
System.out.println(stu1.hashCode());
System.out.println(stu2.hashCode());
//重写hashCode前
//990368553
//1096979270
//重写hashCode后
//-1461067292
//-1461067292
//哈希碰撞
System.out.println("abc".hashCode());
System.out.println("acD".hashCode());
//96354
//96354
}
}
HashSet 的底层原理
1、创建一个默认长度为16,默认加载因子为0.75的数组,数组名为table
2、根据元素的哈希值跟数组的长度计算处应存入的位置
int index = (数组长度-1) & 哈希值
3、判断当前位置是否为null,如果是null直接存入
4、如果位置不为null,表示有元素,则调用equals方法比较属性值
5、属性值一样:不存 属性值不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
注:
1、当数组中存储的元素>16(数组长度)*0.75(加载因子)时,数组成原来的扩容两倍
2、当链表长度大于8而且数组长度大于等于64时,当前链表会转成红黑树以加快访问速度
3、如果集合中存储的是自定义对象,必须要重写hashCode和equals方法
LinkedHashSet的底层原理
1、有序、不重复、无索引
有序是指存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
TreeSet的特点
1、不重复、无索引、可排序
2、可排序:按照元素的默认规则(由小到大)排序
3、TreeSet集合的底层是基于红黑树的数据结构实现排序的,增删改查性能都较好
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Integer> ts=new TreeSet<>();
ts.add(1);
ts.add(2);
ts.add(4);
ts.add(5);
ts.add(3);
System.out.println(ts);
//[1, 2, 3, 4, 5]
//迭代器
Iterator<Integer> it=ts.iterator();
while(it.hasNext()){
Integer i=it.next();
System.out.print(i+" ");
}
System.out.println();
//增强for
for (Integer t : ts) {
System.out.print(t+" ");
}
System.out.println();
//Lambda
ts.forEach(in-> System.out.print(in+" "));
//1 2 3 4 5
//1 2 3 4 5
//1 2 3 4 5
}
}
TreeSet集合默认的规则
1、对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
2、对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序
TreeSet的两种比较方式:
1、默认排序/自然排序:javabean类实现Comparable接口指定比较规则
//创建学生类
public class Student implements Comparable<Student>{
private String name;
private int age;
//重写compareTo,按照成绩升序排序
//this表示当前要添加的元素
//o表示已存在在红黑树中的元素
//返回值:负数:认为要添加的元素是小的,存左边
//正数:认为要添加的元素是大的,存右边
//0:认为要添加的已经存在,舍弃
@Override
public int compareTo(Student o) {
return this.getAge()-o.getAge();
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
//实现类
public class Test {
public static void main(String[] args) {
//创建对象
Student stu1=new Student("zhangsan",24);
Student stu2=new Student("lisi",23);
Student stu3=new Student("wangwu",25);
TreeSet<Student> ts = new TreeSet<>();
ts.add(stu1);
ts.add(stu2);
ts.add(stu3);
System.out.println(ts);
//[Student{name = lisi, age = 23}, Student{name = zhangsan, age = 24},
//Student{name = wangwu, age = 25}]
}
}
2、比较器排序:创建TreeSet对象的时候,传递比较器Comparator制定规则
使用原则:默认使用第一种,如果第一种不能满足当前需求,就使用第二种
public class TreeSetDemo2 {
public static void main(String[] args) {
/*
* 按照长度排序,如果长度一样则按照首字母排序
*/
//创建集合
//o1:表示当前要添加的元素
//o2:表示已经在红黑树存在的元素
//返回值:负数:认为要添加的元素是小的,存左边
//正数:认为要添加的元素是大的,存右边
//0:认为要添加的已经存在,舍弃
TreeSet<String> ts = new TreeSet<>((o1, o2) -> {
//按照长度排序
int i = o1.length() - o2.length();
i = i == 0 ? o1.compareTo(o2) : i;
return i;
});
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
System.out.println(ts);
//[c, ab, df, qwer]
}
}
默认采取第一种排序方式
总结:
1、如果想要集合中的元素可重复:
用ArrayList集合,基于数组的。
2、如果想要集合中的元素可重复,而且当前的增删操作明显多于查询:
用LinkedList集合,基于链表的。
3、如果相对集合中的元素去重:
用HashSet集合,基于哈希表的。
4、如果想对集合中的元素去重,可且保证存取顺序:
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
5、如果想对集合中的元素进行排序:
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。