Set 集合复习
说明: set集合在某种程度上相当于Collection ,但是Set不允许有重复的元素值,如果两个相同的元素试图添加入集合中则添加操作失败,add()方法返回false.
Set判断两个对象是否相同不是使用==,而是根据equals方法,也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象,相反,只要两个对象用equals方法比较返回false,Set就会接受这两个对象。(在极端的情况下,即使equals返回false也有两个对象是一个对象的情况发生,这时Set就会接受这两个对象)
下面看一下三个主要的实现类
HashSet类
实例1:
/**
* Set接口方法演示
* @author fcs
*
*/
public class HashSetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
Set<String> set1 = new HashSet(); //这种写法并没有错,因为HashSet也支持了泛型,但是很少有人这样写,
set1.add("set1");
set.add("element");
System.out.println("set1 == null ? "+set1.isEmpty()); //判断一个set集合是否为空.
Set<String> sonSet = new HashSet<String>();
sonSet.add("son1");
Set<String> sonSet2 = new HashSet<String>();
sonSet2.add("son2");
sonSet2.add("son3");
set.addAll(sonSet); //将集合sonSet加入set中,相当于两个集合的并集(相当于集合之间的并集操作 )
boolean b = set.add("son1"); //此时再将son1加入set集合中,则与原先存在于集合中的元素(element,son1)中的一个元素相等而冲突,因此该方法
//返回false.在下面的迭代中只会出现一个son1.
System.out.println("b = "+b);
System.out.println("set1 == set ? "+(set1 == set)+"\n"); //创建了两个set对象
set.contains("son1"); //是否包含泛型元素,返回true/false;
set.containsAll(sonSet) ; //是否包含另一个Collection的所有元素,返回true/false。
Iterator<String> it = set.iterator();
while(it.hasNext()){
System.out.println(" ---- >"+it.next());
}
boolean b1 = set.remove("element"); //移除指定元素,如果set中存在指定元素,将其移除后返回true,否则返回false
System.out.println("b1 = "+b1);
boolean b2 = set.removeAll(sonSet2) ; //将集合sonSet2中元素与set元素相同的元素去掉,如果set在进行交集操作的时候发生更改了,则返回false,否则返回true(相当于集合之间的交集操作 )
System.out.println("b2 = "+b2);
System.out.println(")))0"+set.contains("son1"));
System.out.println("---"+sonSet2.contains("son1"));
sonSet2.add("son1");
boolean b3 = set.retainAll(sonSet2) ; //将集合sonSet2中元素与set元素相同的保留,不相同的则去除,如果set在进行差集操作的时候发生更改了,则返回false,否则返回true(相当于集合之间的差集操作)
System.out.println("b3 = "+b3);
System.out.println("size = "+set.size()); //返回set集合中的元素个数
set.add("1234");
set.add("array");
Object setArray = set.toArray(); //这种方式也可以编译通过,但是是Object类型了,因此没有任何意义
Object[] setArray1 = set.toArray();
System.out.println(setArray1 == setArray);
System.out.println("length = "+setArray1.length);
}
}
ashSet源码解析
API文档介绍:
该类实现了Set接口,继承了AbstractSet,另外实现了Cloneable,Seriralizable接口
内部由HashMap支持,不保证迭代的顺序不变。
注意该实现不是同步的,如果多线程对该set进行操作的话会引发异常。
内部数据结构是哈希表,hashSet不允许重复,但是可以允许使用null元素。
hashSet的构造函数
1.构造一个空的hashSet。
public HashSet() { map = new HashMap<>(); }
2.根据给定集合构造hashSet,在构造函数中指定装载因子和容量
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
3. 给定装载因子和初始化容量
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
4.给定容量
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
5. 私有构造方法,内部使用LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
注意这里有一个公式:
(e==null ? e2==null : e.equals(e2))
假设e不是该set的元素,而e2是set中的一个元素
(e==null ? e2==null : e.equals(e2))
首先当e是null的时候 返回 e2==null,
如果e2不等于null,则结果返回false,说明该set之前没有null,可以加入e是null的这个值
如果e2等于null,则结果返回 true,说明该set之前已经存在null值了,不能插入该null值了
当e不是null的时候,返回e.equals(e2)
如果e.equals(e2)是真,则结果返回true,则说明该set中已经存在与e相同的值了,不能插入(修改)该值了
如果e.equals(e2)是假,则结果返回false,则说明该set中没有与e相同的值,则可以插入(修改)该值。
这也是set集合判断元素是否唯一的重要依据。
6.hashSet集合判断的标准验证:
public class SetEquals {
public static void main(String[] args) {
Set<Object> set =new HashSet<Object>();
set.add(new A());
set.add(new A());
set.add(new B());
set.add(new B());
set.add(new C());
set.add(new C());
System.out.println(set);
}
}
//只重写equals方法
class A{
public boolean equals(Object obj){
return true;
}
}
//只重写hashCode方法
class B{
public int hashCode(){
return 1;
}
}
//都重写了
class C{
public int hashCode(){
return 2;
}
public boolean equals(Object obj){
return true;
}
}
输出结果:[cn.com.set.B@1, cn.com.set.B@1, cn.com.set.C@2,cn.com.set.A@5f5660ef, cn.com.set.A@1ff61bcf]
说明只有重写了hashcode和equals方法判断重复才有效
这里给出重写hashCode方法的规则:
1.在程序运行中,同一个对象多次调用hashCode 方法应该返回相同的值,
2.当两个对象通过equals方法比较返回true的时候,这两个对象的hashCode()方法返回相等的值,
3.对象中用作equals方法比较标准的Field,都应该用来计算hashCode值
总结
哈希表判断元素是否相同:
1.判断的是两个元素的哈希值是否相同,如果相同,再判断两个对象的内容是否相同
2.判断哈希值相同,HashCode方法(返回此字符串的哈希码),判断内容相同,equals方法
注:哈希值不同,就不需要第二次判断内容是否相同
当向hashset中添加可变对象的时候,即使hashCode和equals方法返回值都相同,也不一定能保证hashset中有相同的值。
package cn.com.set;
import java.util.HashSet;
class R{
int count;
public R(int c ){
this.count = c;
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj != null && obj.getClass() == R.class){
R r = (R)obj;
if(r.count == this.count){
return true;
}
}
return false;
}
//根据count计算hashcode值
public int hashCode(){
return this.count;
}
}
public class SetEquals2 {
public static void main(String[] args) {
HashSet has = new HashSet();
has.add(new R(5));
has.add(new R(-3));
has.add(new R(9));
has.add(new R(-2));
System.out.println(has);
java.util.Iterator it = has.iterator();
R first= (R)it.next();
first.count = -3; //这里把5改成-3
System.out.println(has); //出现重复
has.remove(new R(-3));将原来的那个-3删除
//false
System.out.println("has是否包含count为-3的R对象?"+has.contains(new R(-3)));
//false
System.out.println("has是否包含count为5的R对象?"+has.contains(new R(5)));
}
}
当需要向hashSet中添加可变对象时,应该更加小心
LinkedHashSet集合使用:
linkedHashSet也是根据hashCode 方法决定存储位置。同时使用链表维护元素的次序,这样可以让元素看起来是以插入的顺序保存的。linkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet。该类的实现同样不是线程同步的,也允许为null,不允许重复。
API说明:具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序。
此类提供所有可选的 Set 操作,并且允许 null 元素。与 HashSet 一样,它可以为基本操作(add、contains 和 remove)提供稳定的性能,假定哈希函数将元素正确地分布到存储段中。由于增加了维护链接列表的开支,其性能很可能会比HashSet 稍逊一筹,不过,这一点例外:LinkedHashSet 迭代所需时间与 set 的大小 成正比,而与容量无关。HashSet 迭代很可能支出较大,因为它所需迭代时间与其容量成正比。
链接的哈希 set 有两个影响其性能的参数:初始容量 和加载因子。它们与 HashSet 中的定义极其相同。注意,为初始容量选择非常高的值对此类的影响比对 HashSet要小,因为此类的迭代时间不受容量的影响
实例(按插入顺序输出元素值,构造方法说明):
import java.util.Collection;
import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
public static void main(String[] args) {
/*
* 构造一个带默认初始容量 (16) 和加载因子 (0.75) 的新空链接哈希 set。
*/
LinkedHashSet lhs = new LinkedHashSet();
/*
* 构造一个指定容量为100,默认加载因子为0.75的链接哈希set
*/
LinkedHashSet lhs1 = new LinkedHashSet(100);
Collection coll = new ArrayList();
coll.add("2839248");
/*
* 根据给定的集合构造新的链接哈希set
*/
LinkedHashSet lhs2 = new LinkedHashSet(coll);
/*
* 同时指定初始容量和加载因子的构造方法
*/
LinkedHashSet lhs3 = new LinkedHashSet(100,0.80f);
lhs.add("string1");
lhs.add("string2");
lhs.add("string3");
lhs.add("string4");
lhs.remove("string3");
System.out.println(lhs);
lhs.add("string5");
System.out.println(lhs);
}
}