Collection子接口之二:Set接口
- Set接口是Collection的子接口,set接口没有提供额外的方法
- Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败
- Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法
1.set接口的整体框架
|----Collection接口:单列集合,用来存储一个一个的对象
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|----TreeSet:可以按照添加对象的指定属性,进行排序。
2.set接口的说明
HashSet
1.HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
2.HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet 具有以下特点:
1. 不能保证元素的排列顺序
2. HashSet 不是线程安全的
3. 集合元素可以是 null
3.HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相
4.对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
Set:存储无序的、不可重复的数据
以HashSet为例说明:
1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
二、添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构。
向HashSet中添加元素的过程:
1. 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)
- 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
- 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相
等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
Set set = new HashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
/* 不同顺序输出 但不表示每一次启动都是不同的顺序,只是添加到set里面的那次不同
AA
CC
129
456
123
User{name='Tom', age=12}*/
====================重写方法[idea 自动导入]========================
@Override
public boolean equals(Object o) {
System.out.println("User equals()....");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() { //return name.hashCode() + age;
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
//复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
LinkedHashSet
- LinkedHashSet 是 HashSet 的子类
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
- LinkedHashSet 不允许集合元素重复。
//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个
//数据和后一个数据。
//优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
@Test
public void test2(){
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
/* 就是按照添加顺序的方式输出
456
123
AA
CC
User{name='Tom', age=12}
129
*/
}
}
Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("刘德华", 1001));
TreeSet
-
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
-
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
-
有序,查询速度比List快
TreeSet 要注意的点:
1.向TreeSet中添加的数据,要求是相同类的对象。
TreeSet set = new TreeSet();
//失败:不能添加不同类的对象
set.add(123);
set.add(456);
set.add("AA");
set.add(new User("Tom",12));
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator),默认使用自然排序
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
/*Input:
User{name='Tom', age=12}
User{name='Mike', age=65}
User{name='Jim', age=2}
User{name='Jerry', age=32}
User{name='Jack', age=33}
User{name='Jack', age=56}
*/
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
3.1 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable
接口。因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同
一个类的对象
3.2 向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
3.3 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值。
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
4.1 利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
4.2 此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。
4.3 使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Mary",33));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
/*Input
User{name='Jim', age=2}
User{name='Tom', age=12}
User{name='Jerry', age=32}
User{name='Mary', age=33}
User{name='Jack', age=56}
User{name='Mike', age=65}
*/
注意点:
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(343);
coll.add(343);
循环方式
coll.forEach(System.out::println);
public static List duplicateList(List list){
HashSet h = new HashSet();
h.addAll(list);
return new ArrayList(h);
}
@Test
public void Test2(){ //除去重复的元素 使用Hashset的特性
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(new Integer(1));
arrayList.add(new Integer(1));
arrayList.add(new Integer(2));
arrayList.add(new Integer(2));
arrayList.add(new Integer(4));
arrayList.add(new Integer(4));
List list2 = duplicateList(arrayList);
for (Object i :list2){
System.out.println(i);
}
}
public void Test3(){
HashSet<Object> objects = new HashSet<>();
Person p1 = new Person(80281, "Tom");
Person p2 = new Person(30281, "MMA");
Person p3 = new Person(40581,"JoY");
objects.add(p1);
objects.add(p2);
objects.add(p3);
System.out.println(objects);
//[Person{id=30281, name='MMA'}, Person{id=40581, name='JoY'}, Person{id=80281, name='Tom'}]
p1.name = "DDD"; //修改集合中某个属性 = 添加了新的
objects.remove(p1); //原来的删除 后面的还保留
System.out.println(objects);
//[Person{id=30281, name='MMA'}, Person{id=40581, name='JoY'}, Person{id=80281, name='DDD'}]
objects.add(new Person(80281,"DDD")); //多一个新的
System.out.println(objects);
//[Person{id=10001, name='DDD'}, Person{id=30281, name='MMA'}, Person{id=40581, name='JoY'}, Person{id=80281, name='DDD'}]
objects.add(new Person(80281,"Tom"));
System.out.println(objects);
//[Person{id=10001, name='DDD'}, Person{id=30281, name='MMA'}, Person{id=40581, name='JoY'}, Person{id=80281, name='DDD'}, Person{id=80281, name='Tom'}]