Set是Collection子接口.模拟了数学上的集合概念
set集合存储特点:
- 不允许元素重复,当添加两个一样的元素的时候,添加会失败,add()方法返回false,判断两个对象是否一样使用equals()而不是使用==,返回true的话就是一样的则无法添加到集合.
- 不会记录元素的添加循序,它会按照H内部的hash算法排序
在HashSet中如何判断两个对象是否相等的问题
- 两个对象的equalse比较相等,返回true,则说明是相同的对象
- 两个对象的hashCode方法返回值相等
对象的HashCode值决定的在哈希表中的存储位置
二者缺一不可
当往HashSet集合中添加新的对象的时候,先判断该对象和集合中的对象hashCode值 - 不等直接把新的对象储存在hashCode指定的位置
- 相等,在判断新的对象和集合中对象的equals作比较
1.hashCode相同,equals为true,则视为同一个对象,不保存在哈希表中
2.hashCode相同,但是equals不同,存储在之前对象同槽位的链表上(非常麻烦)
对象的hashCode和equals方法的重要性
每一个存储到哈希表中的对象都得提供HashCode和equals方法来判断是否是同一个对象
存储在Hash表中的对象应该覆盖equals方法和hashCode方法并且在equals相等的同时hashCode也应该相等(在我们自定一对象类的时候需要注意)
hashSet比较对象的具体步骤
当向hashSet集合中存入一个新的元素的时候,HashSet会先调用对象的HashCode方法来得到该对象的HasCode值,然后决定该对象在HashSet中的存储位置
如果两个元素在equals比较返回为true,但是它们的HashCode值返回不同,则它们HashSet会把两个元素储存在不同位置.
不同的数据如何计算hashCode值
开发工具中可以自动生成equals和hashCode方法
如果需要把我们自定义的对象存储到Hash表中,该类型的对象应该覆盖掉equals方法和hashCode方法,并且在该方法中提供自己的判断规则,可以使用eclipse自动生成方法
LinkedHashSet
底层有Hash算法和链表算法两个算法
hash表:用来保证唯一性
链表:用来记录元素的先后循序
是HashSet集合的子类,它在Set中的关系是这个样子
LinkedHashSet→HashSet→Set
特点
不允许重复,但是记录数添加循序
TreeSet
TreeSet底层使用红黑树算法,会对存储的的元素使用自然排序(从小到大),前提是必须保证TreeSet集合中的元素是相同的数据类型,否则报错(不是同一类型无法比较).
TreeSet实现了NavigableSet和 SortedSet这两个特别的接口,NavigableSet接口又实现了SortedSet接口,SortedSet接口实现了Set接口,所以它们的关系是TreeSet→NavigableSet→SortedSet→Set这个亚子的,
TreeSet的排序规则
自然排序
TreeSet调用其中元素的compareTo方法来比较元素的大小关系,然后将集合按照升序排列(从小到大).所以在存入TreeSet中的元素必须实现 Comparable这个接口,并且重写它的compareTo(T o)方法.
如果存入的对象没有实现Comparable这个接口,存入数据的时候就会报无法转换为Comparable的异常
compareTo()方法就是比较方法,自己定义,案列如下
package com.wp;
import java.util.Set;
import java.util.TreeSet;
//实现这个Comparable接口
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
//自己编写比较规则,这里是按照年龄大小来排序
@Override
public int compareTo(Person other) {
if(this.age>other.age) {
return 1;
}else if(this.age<other.age) {
return -1;
}
return 0;
}
//测试
public static void main(String[] args) {
Set<Person> set = new TreeSet<>();
Person p = new Person("张三", 19);
Person p1 = new Person("jack", 15);
Person p2 = new Person("tom", 20);
Person p3 = new Person("Sparrow", 20);
System.out.println(set.add(p));
System.out.println(set.add(p1));
System.out.println(set.add(p2));
System.out.println(set.add(p3));
System.out.println(set);
}
}
最后打印结果为:
true
true
true
false
[Person [name=jack, age=15], Person [name=张三, age=19], Person [name=tom, age=20]]
由此可以得出TreeSet认为如果compareTo()方法返回一个0,它就将这个数据认为同一个对象,重复了,不保存,我们这里是只针对年龄进行的比较,相等的时候返回0,所以年龄有相等的数据的时候它没有保存,也就是保存失败,add()方法返回了一个false,返回正数就往后排(我们这里返回的正数为1),返回负数就往前排序(我们这里返回的负数是-1),注意:自然排序意为从小到大排序,还是不要搞反(从大到小)比较好
定制排序
在TreeSet构造器中传递一个实现了Comparator接口的对象,并重写public int compare()方法,这里的比较方法是按照名字长度排序
案列如下
package com.wp;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Person {
private String name;
private int 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 Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
//测试
public static void main(String[] args) {
Set<Person> set = new TreeSet<>(new NameLength());
Person p = new Person("张三", 19);
Person p1 = new Person("jack", 15);
Person p2 = new Person("tom", 20);
Person p3 = new Person("Spa", 20);
System.out.println(set.add(p));
System.out.println(set.add(p1));
System.out.println(set.add(p2));
System.out.println(set.add(p3));
System.out.println(set);
}
}
//按名字长度自定义的比较器
class NameLength implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
if(o1.getName().length()>o2.getName().length()) {
return 1;
}else if(o1.getName().length()<o2.getName().length()) {
return -1;
}
return 0;
}
}
打印结果为:
true
true
true
false
[Person [name=张三, age=19], Person [name=tom, age=20], Person [name=jack, age=15]]
TreeSet排序总结
定制排序和自然排序二选一,或者两个都写但是只会执行其中一个(构造器中的优先级比较高),不然无法保存.
两种排序返回0就认为是相同的对象,不保存
Set接口总结
相同点:
- 都不允许元素重复
- 都不是线程安全的(解决方案:Collections中的synchronizedSet(Set s))方法
HashSet: - 不保证元素的先后添加循序
- 底层采用hash表算法,查询效率极高,Hash表就是一个优化的数组,所以查询效率比数组还高
- 判断两个对象是否相等
(1) equals比较为true
(2) hashCode值相同
LinkedHashSet: - HashSet的子类,底层采用的是哈希表算法,但是也采用了链表算法来维持元素添加的先后循序,判断两个元素是否相等的规则与HashSet相同
- 应为需要多使用一个链表记录元素的循序,所以性能相当于HashSet较低
一般少用,除非要求集合纪要保证记录添加循序以及不重复才会使用
TreeSet: - 不保证元素的先后添加循序,但是会对集合中的元素做排序操作,
- 底层使用红黑树算法(树结构,比较擅长做范围查询)
-TreeSet要么有自然排序,要么有定制排序
自然排序:要求TreeSet保存的对象必须实现compareable接口,并且重写compareTo()方法
定制排序:要求往TreeSet构造器中传递一个实现了compareor接口的对象,并且要求实现它的compare()方法
判断元素对象是否重复的规则:
compareTo()/compare()方法返回值为0,即为重复对象