首页
看之前建议先看这一篇
首页:JAVA基础之容器汇总
HashSet
特点
HashSet 是一个没有重复元素的集合,不保证元素的顺序。而且 HashSet 允许有 null 元素。HashSet 是采用哈希算法实现,底层实际是用 HashMap 实现的(HashSet 本质就是一个简化版的 HashMap),因此,查询效率和增删效率都比较高。哈希算法原理是使用就是一个散列表来着,对于怎么计算,怎么处理冲突这个直接百度,这里不展开讲。
优点:查询效率和增删效率高
缺点:线程不安全
代码实现
public class HashSetTest {
public static void main(String[] args) {
//实例化HashSet
Set<String> set = new HashSet<>();
//添加元素
set.add("a");
set.add("bb");
set.add("cc");
set.add("d");
//set元素没有下标索引,只能使用foreach的方式遍历或者后面说的Iterator迭代器的方式
System.out.println("----------------------------------------------");
for(String str : set){
System.out.println(str);
}
//删除元素
System.out.println("set.remove(\"cc\") = " + set.remove("cc"));
System.out.println("----------------------------------------------");
for(String str : set){
System.out.println(str);
}
System.out.println("set.size() = " + set.size());
}
}
结果:
----------------------------------------------
bb
cc
a
d
set.remove("cc") = true
----------------------------------------------
bb
a
d
set.size() = 3
Process finished with exit code 0
可如果set的元素是一个类呢,在没有重写hashCode()和equals方法前,会是这亚子
Set<Student> set2 = new HashSet<>();
//正常情况下这两个我们应该认为是相同的,但却被当成不同的加入到集合中了
set2.add(new Student("yql",18));
set2.add(new Student("yql",18));
System.out.println("----------------------------------------------");
for(Student stu : set2){
System.out.println(stu);
}
结果:
----------------------------------------------
Student{name='yql', age=18}
Student{name='yql', age=18}
重写hashCode()和equals方法后
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@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 &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果:
----------------------------------------------
Student{name='yql', age=18}
结果就正常了,为啥呢?
我们知道HashSet的底层是HashMap实现的,所以这个是HashMap的事,不归HashSet管,不愧是我,哈哈(ಡωಡ)hiahiahia,看源码看着看着也是跳到HashMap那边的。
简单地解释下,就是底层是使用散列表的形式进行存储,而存储需要一个Hashcode,通过Hashcode进行一系列操作(就是高16位与低16位进行异或结果再与数组长度进行与操作)得到一个值,不重写hashCode方法前它们两个类的值默认是不一样的,hashCode一样了为啥还要重写equals方法呢,这也是涉及到hashMap的底层存储问题,都说这里是HashSet了,先打一顿再说(手动狗头),因为不同的值计算得到的hashcode也可能是相同的,所以相同的hashCode如果值内容不一样它会使用链表讲其存储在这个数组下标下用来处理冲突,所以相同的hashcode下可能会有多个值,单向链表来着,在不重写equals方法前,Students直接判断它们两个还是不同。好了,不解释了,再说到HashMap时就没东西写了,到这里就已经可以解决前面的问题了。
至于hashMap的存储结构就是采用哈希表中的链地址法存储的,没学过数据结构的自行百度,解释算法就要长篇大论了。
终于可以退出来讲TreeSet了
TreeSet
TreeSet是可以对元素进行排序的,就是一个有序的容器。有趣的是,它的底层是TreeMap实现的,别问我为什么,问就是问它们的设计者,我不知道,源码是那样写的。
排序规则
TreeSet 内部需要对存储的元素进行排序,因此,我们需要给定排序规则:
- 通过元素自身比较规则(实现Comparable接口的CompareTo方法)
- 通过比较器指定比较规则(实现Comparator接口的compare方法)
代码实现
简单版本:
public static void main(String[] args) {
//实例化
Set<String> set1 = new TreeSet<>();
//添加元素
set1.add("a");
set1.add("d");
set1.add("c");
set1.add("b");
set1.add("a");
//遍历
for(String str : set1){
System.out.println(str);
}
}
结果:
通过实现元素自身的比较规则
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@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 &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
if(this.age > o.getAge()){
return 1;
}
if(this.age == o.getAge()) {
return this.name.compareTo(o.getName());
}
return -1;
}
}
//实例化
Set<Student> set2 = new TreeSet<>();
set2.add(new Student("yql",18));
set2.add(new Student("yql2",20));
set2.add(new Student("yql3",20));
for(Student stu : set2){
System.out.println(stu);
}
结果(按照年龄从小到大,相同则按字符串的字典顺序进行排序):
通过实现外部比较器来实现
为了避免误解,我们重新写一个类Users
public class Users {
private String username;
private Integer userage;
public Users(String username, Integer userage) {
this.username = username;
this.userage = userage;
}
public Users() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Users users = (Users) o;
return Objects.equals(username, users.username) &&
Objects.equals(userage, users.userage);
}
@Override
public int hashCode() {
return Objects.hash(username, userage);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getUserage() {
return userage;
}
public void setUserage(Integer userage) {
this.userage = userage;
}
@Override
public String toString() {
return "Users{" +
"username='" + username + '\'' +
", userage=" + userage +
'}';
}
}
实现一个外部的比较器
public class UserComparator implements Comparator<Users> {
@Override
public int compare(Users o1, Users o2) {
if(o1.getUserage() > o2.getUserage()){
return 1;
}
if(o1.getUserage() == o2.getUserage()){
return o1.getUsername().compareTo(o2.getUsername());
}
return -1;
}
}
实例化TreeSet时将其传进去
//传入一个外部比较器
Set<Users> set3 = new TreeSet<>(new UserComparator());
set3.add(new Users("yql",18));
set3.add(new Users("yql2",20));
set3.add(new Users("yql3",20));
System.out.println("---------------------------------------");
for(Users user : set3){
System.out.println(user);
}
结果跟前面一致,两者达到的效果是一样的:
底层是通过TreeMap实现的,到hashMap再看。