1.Hashset
HashSet 底层数据结构是哈希表. HashSet 不是线程安全的 集合元素可以是 null
哈希表:是一个元素为链表的数组,综合了数组和链表的优点。
特点:
元素唯一 无序 底层是链表加数组
遍历集合:
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("李易峰");
set.add("杨洋");
set.add("陈钰琪");
set.add("陈钰琪");
for (String s : set) {
System.out.println(s);
}
}
运行结果:
陈钰琪
李易峰
杨洋
结论:向集合中添加了两个“陈钰琪”但是只打印出现了一个可以说明hashset集合是元素唯一,从打印的顺序来看并没有向添加的那个顺序一样可以说明hashset集合是无序的。
2.Hashset原理
我们新建一个学生类然后在hashcode里面存放student对象。然后打印看有什么变化。
代码实现:
//学生类
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;
}
}
//
public class MyTest {
public static void main(String[] args) {
Student s = new Student("宫崎骏", 30);.//获取学生类对象
Student s2 = new Student("王祖贤", 31);
Student s3 = new Student("周星驰", 32);
Student s4 = new Student("陈钰琪", 33);
Student s5 = new Student("宫崎骏", 30);
Student s6 = new Student("陈钰琪", 33);
Student s7 = new Student("陈钰琪", 33);
HashSet<Student> set = new HashSet<>();//创建hashset集合
set.add(s);//存入student对象
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
set.add(s6);
set.add(s7);
//遍历集合
for (Student student : set) {
System.out.println(student.getName()+"=="+student.getAge());
}
}
}
运行结果:
王祖贤31
宫崎骏30
陈钰琪33
陈钰琪33
陈钰琪33
宫崎骏30
周星驰==32
我们可以观察到集合中存了两个相同的“宫崎骏”,和“陈钰琪”两个相同的存进了集合,这会不会和hashset集合冲突呢?hashset不是元素唯一吗?为什么可以存进去呢?
接下来
我们重写student类的equals()方法和hashCode()方法。
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;
}
//重写equals方法
@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);
}
//重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
运行结果:
宫崎骏30
周星驰32
王祖贤31
陈钰琪33
结论:
我们可以观察到重复的元素都没有的,保证了hashSet的唯一性。原因是自定义的类重写了hashCode()方法是为了确定元素在哈希表中的存放位置从而减少碰撞。
重写equals()方法是保证了hashSet集合的唯一性。hashSet集合底层是通过链表和数组来存取的通过计算出hashCode的值来确定我们把元素存在哪里。
3.linkedHashset集合
底层数据结构是链表和哈希表,元素有序唯一。
public class MyTest {
public static void main(String[] args) {
Student s = new Student("宫崎骏", 30);
Student s2 = new Student("王祖贤", 31);
Student s3 = new Student("周星驰", 32);
Student s4 = new Student("陈钰琪", 33);
Student s6 = new Student("陈钰琪", 33);
Student s7 = new Student("陈钰琪", 33);
LinkedHashSet<Student> set = new LinkedHashSet<>();//创建linkedhashset集合
set.add(s);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s6);
set.add(s7);
for (Student student : set) {
System.out.println(student.getName()+"=="+student.getAge());
}
}
}
运行结果:
宫崎骏30
王祖贤31
周星驰32
陈钰琪33
结论:
我们可以观察到元素有序和添加时的顺序一致,而且元素唯一,链表保证了元素有序,哈希表保证了元素唯一。自定义类也需要重写hashCode()方法和equals()方法来保证元素的唯一性。其他用法和hashset一致只不过比hashset 不同的是元素是否有序。
4.treeSet集合
A: TreeSet集合的特点: 元素唯一,并且可以对元素进行排序
排序:
a: 自然排序
b: 使用比较器排序
public class MyTest {
public static void main(String[] args) {
TreeSet<Integer> treeSet = new TreeSet<>();//新建treeSet集合
treeSet.add(10);//添加integer类型数据
treeSet.add(5);
treeSet.add(3);
treeSet.add(520);
treeSet.add(66);
treeSet.add(99);
treeSet.add(5);
//遍历集合
for (Integer integer : treeSet) {
System.out.println(integer);
}
}
}
运行结果:
3
5
10
66
99
520
我们可以观察到遍历出来的集合元素唯一而且已经帮我们排好了序。原因他的底层数据结构是二叉树。接下来我们画一画他是怎么存取的。
二叉树:
所以我们取出来集合中的元素就已拍好了序。
5.treeSet集合的特点
treSet集合排序方式:自然排序 和比较器排序
- 自然排序
如果使用自然排序,那么元素是有要求的,要求元素必须实现一个comparable接口重写里面的comparable To方法,根据此方法的返回值的正负0,来决定元素在二叉树的位置。
实例:
我们新建一个学生类然后用treeSet存在集合中然后通过年龄的大小来对其排序
//学生类 //实现了comparable接口 重写comparable To方法
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;
}
@Override
public int compareTo(Student o) {
int num=this.age-o.age;
return num;
}
}
public class MyTest {
public static void main(String[] args) {
Student s = new Student("张三2", 34);//学生类对象
Student s2 = new Student("张三1", 34);
Student s3 = new Student("张三312", 3);
Student s4 = new Student("张三21", 32);
Student s5 = new Student("张三1", 3);
Student s6 = new Student("张三5", 377);
Student s7 = new Student("张三6", 3);
TreeSet<Student> treeSet = new TreeSet<>();//创建treeSet集合
treeSet.add(s);//添加元素
treeSet.add(s2);
treeSet.add(s3);
treeSet.add(s4);
treeSet.add(s5);
treeSet.add(s6);
treeSet.add(s7);
//遍历集合
for (Student student : treeSet) {
System.out.println(student.getName()+"---"+student.getAge());
}
}
}
运行结果:
张三312—3
张三21—32
张三2—34
张三5—377
我们可以观察到虽然是按照年龄排序了,但是还是有一点缺陷年龄相同但是名字不同的也没有存进去不是同一个人也没有存进去,所以我们还需要改进一下
然后我们再次对重写过后的comparable To方法做出改进
@Override
public int compareTo(Student o) {
int num=this.age-o.age;
int num2=num==0?this.name.compareTo(o.name):num;
return num2;
}
运行结果:
张三1—3
张三312—3
张三6—3
张三21—32
张三1—34
张三2—34
张三5—377
结论:
这下我们可以观察到按照年龄的大小排序了出来,在我们自定义的类中使用treeSet集合自然排序要注意实现Comparable接口重写compara To方法然后用treeSet集合来存取,否则JVM无法判断你要到底使用什么排序来存取的数据,就会报错。
- 比较器排序:
我们必要要传入一个比较器
自定义一个比较器
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
int num=s1.getAge()-s2.getAge();
int num2=num==0? s1.getName().compareTo(s2.getName()) :num;
return num2;
}
}
然后我们把比较器传给treeSet集合它就知道怎么比较了。
MyComparator comparator = new MyComparator();
TreeSet<Student> treeSet = new TreeSet<>(comparator);
这时候我们就不需要重写自定类的comparable To方法和实现comparable接口也可以比较了。
如果我们觉得麻烦可以直接在new treeSet集合的时候直接new一个比较器传进去。
TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num=s1.getAge()-s2.getAge();
int num2=num==0? s1.getName().compareTo(s2.getName()) :num;
return num2;
}
});
结论:
我们在使用比较器的时候不管是自然排序或者是比较器排序,都可以使用,在使用自然排序的时候要注意在自定义的类中实现comparable接口以及重写compara To方法。在使用比较器排序的我们需要传一个比较器进去。