愚公系列】2023年10月 Java教学课程 048"/>
【愚公系列】2023年10月 Java教学课程 048
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
文章目录
- 🚀一、Set集合
- 🔎1.Set集合
- 🦋1.1 Set集合概述和特点
- 🦋1.2 Set集合的使用
- 🔎2.HashSet集合
- 🦋2.1 HashSet集合概述和特点
- 🦋2.2 HashSet集合基本使用
- 🦋2.3 哈希值
- 🦋2.4 哈希表结构
- 🦋2.5 案例
- 🔎3.LinkedHashSet集合
- 🦋3.1 LinkedHashSet集合概述和特点
- 🦋3.2 LinkedHashSet集合基本使用
- 🔎4.TreeSet集合
- 🦋4.1 TreeSet集合概述和特点
- 🦋4.2 TreeSet集合基本使用
- 🦋4.3 自然排序Comparable的使用
- 🦋4.4 自定义排序规则
- 🦋4.5 两种比较方式总结
- 🚀感谢:给读者的一封信
🚀一、Set集合
🔎1.Set集合
🦋1.1 Set集合概述和特点
Set是Java中的一个集合接口,它的特点是:
- 不允许重复元素:Set集合中不允许存储重复的元素,如果添加重复元素,新值将被忽略;
- 无序存储:Set集合中元素的顺序是未指定的,元素的插入顺序与取出顺序可能不同;
- 元素可为null:Set集合可以包含null元素。
Set集合的主要实现类有HashSet、TreeSet和LinkedHashSet,它们的区别在于内部实现方式和迭代器的顺序不同:
- HashSet:HashSet通过哈希表实现,元素无序存储,迭代器顺序未指定;
- TreeSet:TreeSet采用红黑树实现,元素按照大小排序,支持自然排序和定制排序;
- LinkedHashSet:LinkedHashSet内部使用HashMap来实现,元素按照插入顺序存储,迭代器顺序与插入顺序相同。
总之,Set集合是一种不允许重复元素的无序集合。使用Set集合能够高效地处理元素的查找和去重操作。
🦋1.2 Set集合的使用
import java.util.HashSet;
import java.util.Set;public class SetExample {public static void main(String[] args) {Set<String> set = new HashSet<>();// 添加元素到Set集合中set.add("apple");set.add("banana");set.add("orange");set.add("apple"); // 添加重复元素// 遍历Set集合中的元素for (String s : set) {System.out.println(s);}// 判断Set集合是否包含某个元素System.out.println(set.contains("grape")); // falseSystem.out.println(set.contains("apple")); // true// 删除Set集合中的元素set.remove("banana");// 清空Set集合中的所有元素set.clear();// 判断Set集合是否为空System.out.println(set.isEmpty()); // true}
}
输出结果为:
orange
banana
apple
false
true
可以看到,Set集合可以很方便地添加、删除、查找、清空元素。同时,由于Set集合的特点,最终只输出三个元素,重复元素被自动去重了。
🔎2.HashSet集合
🦋2.1 HashSet集合概述和特点
HashSet是Java集合框架中的一种实现类,它实现了Set接口,底层以哈希表(散列表)的形式存储数据,具有以下特点:
-
无序性:元素在集合中没有固定的位置,不保证遍历顺序和插入顺序相同。
-
不允许重复元素:HashSet中只能存储唯一的元素,如果添加重复元素,将自动被覆盖或忽略。
-
元素可以为null:HashSet可以存储null元素,但只能存储一个null元素。
-
高效性:HashSet的底层实现是哈希表,查找、插入和删除元素的效率都很高,时间复杂度为O(1)。
需要注意的是,在使用HashSet存储自定义的类型时,需要重写equals()和hashCode()方法,以保证正确性。
🦋2.2 HashSet集合基本使用
HashSet是Java中常用的集合类之一,它实现了Set接口,可以存储不重复的元素。HashSet内部使用哈希表来存储元素,可以快速进行插入、删除和查找操作。下面是HashSet集合的基本应用:
- 创建HashSet集合
可以使用无参构造函数创建一个空的HashSet集合。
HashSet<String> set = new HashSet<>();
- 添加元素到HashSet集合
可以使用add方法向HashSet集合添加元素,如果元素已经存在,则不会被添加。
set.add("apple");
set.add("banana");
set.add("orange");
- 判断HashSet集合是否包含某个元素
可以使用contains方法判断HashSet集合是否包含某个元素。
boolean contains = set.contains("apple");
- 从HashSet集合中删除某个元素
可以使用remove方法从HashSet集合中删除某个元素。
set.remove("apple");
- 遍历HashSet集合
可以使用for-each循环遍历HashSet集合。
for(String item: set) {System.out.println(item);
}
- 获取HashSet集合的大小
可以使用size方法获取HashSet集合的大小。
int size = set.size();
注意:HashSet集合是无序的,因此遍历HashSet集合时元素的顺序不确定。
🦋2.3 哈希值
哈希值是将任意长度的数据映射为固定长度的数据(通常是一个整数),该固定长度的数据就是哈希值。哈希值具有以下特点:
-
相同的输入一定产生相同的输出。
-
不同的输入尽可能产生不同的输出。
-
不同的输入可能产生相同的输出,这种情况称为哈希冲突。
在Java中,哈希值通常是用整数表示的,可以通过对象的hashCode方法获取对象的哈希值。下面是一个示例:
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}public class Main {public static void main(String[] args) {Person person = new Person("Tom", 20);int hashCode = person.hashCode();System.out.println(hashCode);}
}
在上面的示例中,Person类重写了hashCode方法,使用Objects类的hash方法来计算哈希值,该方法会自动将多个属性进行混合计算,从而避免了哈希冲突的问题。可以通过调用hashCode方法获取Person对象的哈希值。
🦋2.4 哈希表结构
HashSet是一个基于哈希表实现的Set集合,它是Java集合框架中Set接口的实现类之一。在JDK1.7版本中,HashSet的实现方式是使用哈希表,它是通过一个数组来实现的,每个数组元素都是一个单向链表的头结点,单向链表中存储的是具有相同哈希码的元素。
HashSet的主要设计思路是:将元素根据哈希值分布到数组的不同位置上,这样可以通过哈希值快速地访问元素,提高了查找、插入和删除操作的效率。
实现原理:
-
创建HashSet对象时,会创建一个初始的数组,数组的长度默认为16,当数组长度不足时,会自动扩容。
-
将添加的元素先计算哈希值,然后根据哈希值%数组长度得到该元素在数组中的下标位置。
-
如果该位置为空,则将元素添加到该位置;如果该位置已经有元素,则遍历该位置上的链表,判断该元素是否已经存在,如果已经存在,则不再添加;否则将元素添加到链表的末尾。如果链表长度超过阈值(默认为8),则将链表转换为红黑树进行存储,这样在查找、删除等操作时可以更快速的定位元素。
-
当元素个数超过数组长度的0.75倍时,会触发扩容操作,将数组长度加倍,并重新计算元素在数组中的位置。
-
当元素被删除时,同样需要根据哈希值找到元素在数组中的位置,如果该位置上只有一个元素,则直接删除;如果该位置上有多个元素,则需要遍历链表或者红黑树,找到需要删除的元素。
HashSet的实现过程比较复杂,但是它的查找、插入和删除操作都是常数级别的,因此在大规模数据操作时,HashSet比较高效。
下面是JDK1.8版本HashSet的实现原理解析:
-
HashSet是基于HashMap实现的,其内部维护了一个HashMap对象,HashMap的键存储HashSet中的元素,HashMap的值则是一个常量对象(DUMMY)。
-
HashSet中的元素是无序的,是根据元素的hashCode值来确定元素在HashMap中的存储位置,并使用链表和红黑树解决哈希冲突。在JDK1.8中,当链表长度超过8时,链表会自动转化为红黑树,以提高查询效率。
-
HashSet中的元素不允许重复,是根据元素的equals方法来判断两个元素是否相等。如果两个元素相等,则后面添加的元素会覆盖前面的元素。
-
HashSet支持添加、删除、查找、遍历等操作,其时间复杂度与HashMap的相同,即O(1)。
HashSet是一种基于HashMap实现的无序、不重复元素集合,可以快速地进行添加、删除、查找和遍历操作。
🦋2.5 案例
-
案例需求
- 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
- 要求:学生对象的成员变量值相同,我们就认为是同一个对象
-
代码实现
学生类
public class Student {private String name;private int 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;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;if (age != student.age) return false;return name != null ? name.equals(student.name) : student.name == null;}@Overridepublic int hashCode() {int result = name != null ? name.hashCode() : 0;result = 31 * result + age;return result;} }
测试类
public class HashSetDemo02 {public static void main(String[] args) {//创建HashSet集合对象HashSet<Student> hs = new HashSet<Student>();//创建学生对象Student s1 = new Student("林青霞", 30);Student s2 = new Student("张曼玉", 35);Student s3 = new Student("王祖贤", 33);Student s4 = new Student("王祖贤", 33);//把学生添加到集合hs.add(s1);hs.add(s2);hs.add(s3);hs.add(s4);//遍历集合(增强for)for (Student s : hs) {System.out.println(s.getName() + "," + s.getAge());}} }
-
总结
HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法
🔎3.LinkedHashSet集合
🦋3.1 LinkedHashSet集合概述和特点
LinkedHashSet是Java中继承自HashSet的一种集合,它与HashSet的不同点在于,它可以保持元素插入的顺序。具体特点如下:
- 继承自HashSet,实现了Set接口,因此元素无重复且无序。
- 内部实现采用哈希表和双向链表结合的方式,保证了元素的插入顺序。
- 迭代器遍历时,按照元素插入的顺序进行遍历。
- 可以存储null元素。
- 由于内部维护了一个双向链表,因此在迭代过程中可以高效地执行插入和删除操作。
LinkedHashSet继承了HashSet的元素去重和快速查找特点,并且保证了元素的插入顺序,因此适用于需要保留元素插入顺序的业务场景下。
🦋3.2 LinkedHashSet集合基本使用
LinkedHashSet是HashSet的一个变体,它通过链表维护元素插入的顺序。这意味着遍历的顺序是元素插入的顺序,而不是元素的哈希码顺序。
LinkedHashSet中的元素具有唯一性,即集合中不包含重复的元素。与HashSet不同,LinkedHashSet允许null元素。
使用LinkedHashSet需要先创建LinkedHashSet对象,然后可以使用以下方法进行操作:
- add(Object o):向集合中添加元素。
- remove(Object o):从集合中移除元素。
- contains(Object o):判断集合中是否包含指定元素。
- size():返回集合中元素的数量。
- clear():清空集合中的所有元素。
- iterator():返回集合中元素的迭代器,可以用于遍历集合中的元素。
以下是LinkedHashSet的基本使用示例:
import java.util.LinkedHashSet;public class Main {public static void main(String[] args) {// 创建一个LinkedHashSet对象LinkedHashSet<String> set = new LinkedHashSet<>();// 往集合中添加元素set.add("A");set.add("B");set.add("C");set.add("D");set.add("E");set.add(null);// 遍历集合中的元素for (String str : set) {System.out.println(str);}// 判断集合中是否包含指定元素System.out.println(set.contains("A"));// 从集合中移除元素set.remove("B");// 清空集合中的所有元素set.clear();// 返回集合中元素的数量System.out.println(set.size());}
}
🔎4.TreeSet集合
🦋4.1 TreeSet集合概述和特点
TreeSet是Java中的一个有序集合类,它内部使用一棵红黑树来维护元素的顺序。TreeSet中的元素按照自然顺序排列(或者按照指定的比较器规则)。
TreeSet的主要特点包括:
-
有序性:TreeSet中元素是有序排列的,可以通过迭代器按照元素顺序遍历集合。
-
独一无二:TreeSet中不允许有重复元素。如果插入一个已经存在的元素,则会覆盖之前的元素。
-
可排序性:TreeSet中元素必须是可比较的,否则会抛出ClassCastException异常。
-
快速访问:使用TreeSet可以快速查找一个元素,时间复杂度为O(log n)。
需要注意的是,TreeSet不是线程安全的,如果多个线程同时访问TreeSet,则需要进行同步处理。
🦋4.2 TreeSet集合基本使用
Java中TreeSet是一个基于红黑树的实现,可以实现自动排序和去重功能。下面是TreeSet集合的基本使用:
1.创建TreeSet集合:
Set<Integer> set = new TreeSet<>();
2.添加元素:
set.add(1);
set.add(2);
set.add(3);
3.遍历集合:
for(Integer num : set){System.out.println(num);
}
4.判断集合是否包含某个元素:
if(set.contains(2)){System.out.println("包含2");
}
5.删除集合中的元素:
set.remove(2);
6.获取集合大小:
int size = set.size();
7.清空集合:
set.clear();
需要注意的是,要使用TreeSet集合存储自定义对象,必须要实现Comparable接口并重写compareTo方法,用于排序。否则会抛出ClassCastException异常。例如:
class Student implements Comparable<Student>{private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Student o) {//按照年龄从小到大排序return this.age - o.age;}//省略getter和setter方法
}Set<Student> set = new TreeSet<>();
set.add(new Student("张三",20));
set.add(new Student("李四",18));
set.add(new Student("王五",22));for(Student stu : set){System.out.println(stu.getName()+" "+stu.getAge());
}
🦋4.3 自然排序Comparable的使用
-
案例需求
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
-
实现步骤
- 使用空参构造创建TreeSet集合
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自定义的Student类实现Comparable接口
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写接口中的compareTo方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
- 使用空参构造创建TreeSet集合
-
代码实现
学生类
public class Student implements Comparable<Student>{private String name;private int 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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {//按照对象的年龄进行排序//主要判断条件: 按照年龄从小到大排序int result = this.age - o.age;//次要判断条件: 年龄相同时,按照姓名的字母顺序排序result = result == 0 ? this.namepareTo(o.getName()) : result;return result;} }
测试类
public class MyTreeSet2 {public static void main(String[] args) {//创建集合对象TreeSet<Student> ts = new TreeSet<>();//创建学生对象Student s1 = new Student("zhangsan",28);Student s2 = new Student("lisi",27);Student s3 = new Student("wangwu",29);Student s4 = new Student("zhaoliu",28);Student s5 = new Student("qianqi",30);//把学生添加到集合ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);//遍历集合for (Student student : ts) {System.out.println(student);}} }
🦋4.4 自定义排序规则
TreeSet是按照元素的自然顺序进行排序的,如果需要自定义排序规则,则需要实现Comparator接口。
下面是一个示例代码,演示了如何在TreeSet中使用自定义的排序规则:
import java.util.*;class Employee {private int id;private String name;private int age;public Employee(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}public int getId() {return id;}public String getName() {return name;}public int getAge() {return age;}
}class EmployeeComparator implements Comparator<Employee> {public int compare(Employee e1, Employee e2) {return e1.getName()pareTo(e2.getName());}
}public class Main {public static void main(String[] args) {TreeSet<Employee> employees = new TreeSet<>(new EmployeeComparator());Employee emp1 = new Employee(1, "John", 25);Employee emp2 = new Employee(2, "Alex", 30);Employee emp3 = new Employee(3, "David", 35);Employee emp4 = new Employee(4, "Mark", 28);employees.add(emp1);employees.add(emp2);employees.add(emp3);employees.add(emp4);for(Employee emp : employees) {System.out.println(emp.getName());}}
}
在上面的示例代码中,EmployeeComparator类实现了Comparator接口,并覆盖了它的compare方法。在compare方法中,我们按照员工的姓名来进行排序。
然后,在主方法中,我们创建了一个TreeSet对象,并传入了EmployeeComparator实例作为构造函数的参数。这样就可以使用自定义的排序规则来排序了。
最后,我们遍历TreeSet中的元素,并输出了它们的姓名,这里按照自定义的排序规则进行了排序。
🦋4.5 两种比较方式总结
- 两种比较方式小结
- 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
- 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
- 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序
- 两种方式中关于返回值的规则
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
更多推荐
【愚公系列】2023年10月 Java教学课程 048
发布评论