set接口
set接口
HashSet里面有一个HashMap(适配器模式)。
Set的接口和他的实现类都是基于对应Map
的来实现,他的存在是为了我们只需要进行对单一数据操作来保证数据不重复等特点的使用的。
存储一组唯一,无序的对象,最多存储一个null值
实现类: HashSet、LinkedHashSet和TreeSet
操作数据的方法与List类似,Set接口不存在get()方法
HashSet:
采用Hashtable哈希表存储结构
添加速度快,查询速度快,删除速度快
唯一,无序,可以为null
HashSet是对HashMap的简单包装,对HashSet的函数调用都会转换成合适的HashMap方法,因此HashSet的实现非常简单,只有不到300行代码。
//HashSet是对HashMap的简单包装
public class HashSet<E>
{
......
private transient HashMap<E,Object> map;//HashSet里面有一个HashMap
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
......
public boolean add(E e) {//简单的方法转换
return map.put(e, PRESENT)==null;
}
......
}
底层原理:哈希表
HashSet底层实现是基于HashMap来实现的,将set中存储的值作为HashMap的key来处理,PRESENT是一个填充的value值(所以是唯一的)
底层原理:数组+链表=哈希表
存入数据时,调用hashcode方法计算哈希值,通过hash值和一个表达式计算在数组中存放的位置,放入hashset中的数据,一定要重写hashCode和equals方法,保持唯一性
;
LinkedHashSet:哈希表+链表
其继承自HashSet,其继承了HashSet中所有的属性和方法
采用哈希表存储结构,同时使用链表维护次序
唯一,有序(添加顺序) ,可以为null
底层原理:
哈希表+总的链表
LinkedHashSet的实现是基于LinkedHashMap来实现的
LinkedHashSet数据有序的特征是基于LinkedHashMap来保证的,其底层利用双向链表来实现的数据有序
其实就是在HashSet的基础上,多了一个总的链表,这个总链表将放入的元素串在一起,方便有序的遍历:
(可以看到LinkedHashMap.Entry 继承自HashMap.Node 除了Node 本身有的几个属性外,额外增加了before after 用于指向前一个Entry 后一个Entry。也就是说,元素之间维持着一条总的链表数据结构。)
TreeSet:
TreeSet底层数据结构采用红黑树(平衡二叉树) 来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造)。
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
此实现为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。
查询速度没有HashSet快
1、二叉树数据自然有序(自定义排序,实现Comparator接口)
2、二叉树中的数据不重复,数据不重复
3、数据不能为null
TreeSet保证元素唯一和排序的原理
TreeSet元素唯一和排序是指:通过比较器比较的元素放入树中进行排序和唯一保证,而不是整个对象;
TreeSet集合底层数据结构是红黑树(平衡二叉树)。 第一个元素存储的时候,直接作为根节点。 从第二个开始,每个元素从根节点开始比较:
- 大——就作为右子叶节点 (保证有序)
- 小——就作为左子叶节点 (保证有序)
- 相等——不做处理(保证唯一性)
放入红黑树的元素是看比较器按哪个属性去比较,相等的不放入二叉树
放入TreeSet的对象必须实现比较器,不然会报错
两种排序(自然排序,比较器排序)
自然排序:内部比较器 让元素所属的类中实现自然排序接口Comparerable接口 。可以在自定义类中实现Comparerable接口,重写compareTo()方法。
比较器排序:外部比较器 让集合的构造方法接收一个比较器Comparator接口的子类对象。可以在自定义类中实现Comparetor接口,重写compare()方法。
自然排序的实现
Integer和String对象都可以进行默认的TreeSet排序
public class TEST {
public static void main(String[] args) {
//Integer类本身已经实现了Comparable接口,重新了compare()方法。
TreeSet<Integer> treeSet = new TreeSet<Integer>(); // 使用自然排序
treeSet.add(10);
treeSet.add(10);
treeSet.add(20);
treeSet.add(18);
for (Integer number : treeSet) {
System.out.println(number); //输出结果: 1 10 14 15 18 20 90 (去重且排序)
}
}
}
对自定义对象进行 自然排序
自然排序要进行一下操作:
1.自定义类中实现 Comparable
2.重写Comparable接口中的compareTo方法
// Student类,实现Comparable接口
public class Student implements Comparable<Student> {
private String name;
private int age;
...
// 重写compareTo方法,内部比较器
@Override
public int compareTo(Student s) {
//return -1; //-1表示放在红黑树的左边,即逆序输出
//return 1; //1表示放在红黑树的右边,即顺序输出
//return 0; //表示元素相同,仅存放第一个元素
//主要条件 姓名的长度,如果姓名长度小的就放在左子树,否则放在右子树
int num = this.name.length() - s.name.length();
//姓名的长度相同,不代表内容相同,如果按字典顺序此 String 对象位于参数字符串之前,则比较结果为一个负整数。
//如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。
//如果这两个字符串相等,则结果为 0
int num1 = num == 0 ? this.name.compareTo(s.name) : num; // this.name.compareTo(s.name) 这个compareTo(String str)方法已经重写,可以直接调用。
//姓名的长度和内容相同,不代表年龄相同,所以还要判断年龄
int num2 = num1 == 0 ? this.age - s.age : num1;
return num2;
}
}
// 测试类
import java.util.*;
public class TEST {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts=new TreeSet<Student>();
//创建元素对象
Student s1=new Student("zhangsan",20);
Student s2=new Student("lis",22);
Student s3=new Student("wangwu",24);
Student s4=new Student("chenliu",26);
Student s5=new Student("zhangsan",22);
Student s6=new Student("qianqi",24);
//将元素对象添加到集合对象中
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历
for(Student s:ts){
System.out.println(s.getName()+"-----------"+s.getAge());
}
}
}
//打印:
lis-----------22
qianqi-----------24
wangwu-----------24
chenliu-----------26
zhangsan-----------20
zhangsan-----------22
比较器排序的实现
比较器排序步骤:
1.单独创建一个比较类,这里以MyComparator为例,并且要让其继承Comparator
2.重写Comparator接口中的compare()方法
3.如果return负数,则认为第一个参数排在前面;如果return正数,则第二个参数排在前面;如果return0,则认为两者相等。
一般比较器写法
// 自定义类: Student类
public class Student {
private String name;
private int age;
public Student(){
super();
}
public Student(String name, int age){
super();
this.name = name;
this.age = age;
}
....
}
// 单独创建的一个比较类,继承Comparator<T>接口,并重写compare()方法
import java.util.Comparator;
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1,Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
}
// 测试类
import java.util.*;
public class TEST {
public static void main(String[] args) {
//创建集合对象
//TreeSet(Comparator<? super E> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。
TreeSet<Student> ts=new TreeSet<Student>(new MyComparator());
//创建元素对象
Student s1=new Student("zhangsan",20);
Student s2=new Student("lis",22);
Student s3=new Student("wangwu",24);
Student s4=new Student("chenliu",26);
Student s5=new Student("zhangsan",22);
Student s6=new Student("qianqi",24);
//将元素对象添加到集合对象中
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历
for(Student s:ts){
System.out.println(s.getName()+"-----------"+s.getAge());
}
}
}
// 打印结果:
lis-----------22
qianqi-----------24
wangwu-----------24
chenliu-----------26
zhangsan-----------20
zhangsan-----------22
常见比较器写法
单独创建一个MyComparator类不是特别好, 可以将MyComparetor的内容直接写到主类中。开发中常用匿名内部类实现。
public class TreeSetDemo {
public static void main(String[] args) {
// 如果一个方法的参数是接口,那么真正要的是接口的实现类的对象
// 而匿名内部类就可以实现这个东西
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
: num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
});
// 创建元素
Student s1 = new Student("linqingxia", 27);
Student s2 = new Student("zhangguorong", 29);
Student s3 = new Student("wanglihong", 23);
Student s4 = new Student("linqingxia", 27);
Student s5 = new Student("liushishi", 22);
Student s6 = new Student("wuqilong", 40);
Student s7 = new Student("fengqingy", 22);
Student s8 = new Student("linqingxia", 29);
// 添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
ts.add(s8);
// 遍历
for (Student s : ts) {
System.out.println(s.getName() + "---" + s.getAge());
}
}
}
内部比较器,自然排序
public class Student implements Comparable<Student> {
...
@Override
public int compareTo(Student o) {
return this.getAge()-o.getAge();
}
}
外部比较器,自定义排序
class BiJiao implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
}
引用对象:实际开发中利用外部比较器多,因为扩展性好(多态)
内部比较器是对象实现:public class Student implements Comparable<Student>
外部比较器是:比较器是单独的一个新对象:class BiJiao01 implements Comparator<Student>
//创建一个TreeSet:
//利用外部比较器,必须自己制定:
/*Comparator<Student> com = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
};*/
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
});//一旦指定外部比较器,那么就会按照外部比较器来比较
ts.add(new Student(10,"elili"));
ts.add(new Student(8,"blili"));
ts.add(new Student(4,"alili"));
ts.add(new Student(9,"elili"));
ts.add(new Student(10,"flili"));
ts.add(new Student(1,"dlili"));
System.out.println(ts.size());
System.out.println(ts);
底层原理:
TreeSet底层是基于treeMap来实现的
底层:二叉树(数据结构中的一个逻辑结构)
TreeSet底层的二叉树的遍历是按照升序的结果出现的,这个升序是靠中序遍历得到的:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?