前几篇文章已经介绍了关于List集合的讲解,今天学习Set集合相关的实现类。
Set集合常用的如:HashSet、TreeSet。HashSet是Set集合的典型实现,HashSet按照Hash算法来存储集合中的元素,存在以下特点:
- 不能保证元素的顺序,元素是无序的
- HashSet是不同步的,需要外部保持线程之间的同步问题,Collections.synchronizedSet(new XXSet());
- 集合元素值允许为null
继承关系
java.util.Collection
| java.util.AbstractCollection<E>
| java.util.AbstractSet<E>
| java.util.HashSet<E>
实现接口
Serializable, Cloneable, Iterable, Collection, Set
基本属性
private transient HashMap<E,Object> map; //map集合,HashSet存放元素的容器
private static final Object PRESENT = new Object(); //map,中键对应的value值
重要方法解析
构造方法
//无参构造方法,完成map的创建
public HashSet() {
map = new HashMap<>();
}
//指定集合转化为HashSet, 完成map的创建
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
//指定初始化大小,和负载因子
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
//指定初始化大小
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
//指定初始化大小和负载因子,dummy 无实际意义
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
通过构造函数可以发现,HashSet底层是采用HashMap实现的。
Add()方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
PRESENT为HashSet类中定义的一个使用static final修饰的常量,其实无实际意义,HashSet的add()方法调用HashMap的put()方法实现,如果键已经存在,map.put()放回的是旧值,添加失败。如果添加成功map.put()方法返回的是null,HashSet.add()方法返回的true,则添加的元素可以作为map中的key。
简单例子:
/**
* 测试:
* 1、hashSet不存重复元素
* 2、存和取是无序的
* @author Microtao
*
*/
public class HashSetTest {
public static void main(String[] args) {
Set hs = new HashSet();
hs.add("a");
hs.add("b");
hs.add("1");
hs.add("2");
hs.add("e");
hs.add("e");
Iterator it = hs.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
运行结果:a 1 b 2 e
HashSet存放的是哈希值,Hashset存储元素的顺序并不是按照存入时的顺序(和List显然不同),是按照哈希值来存的,所以取数据也是按照哈希值取的。HashSet不存入重复元素的规则:使用hashcode和equals。 那么HashSet是如何检查重复?其实原理:HashSet会通过元素的hashcode()和equals()方法进行判断,当试图将元素加入到Set集合中,HashSet首先会使用对象的hashcode来判断对象加入的位置。同时也会与其他已经加入的对象的hashcode进行比较,如果没有相等的hashcode,HashSet就认为这个对象之前不存在,如果之前存在同样的hashcode值,就会进一步的比较equals()方法,如果equals()比较返回结果是true,那么认为该对象在集合中的对象是一模一样的,不会将其加入;如果比较返回的是false,那么HashSet认为新加入的对象没有重复,可以正确加入。 如图所示:当两个对象的hashcode不一样时,说明两个对象是一定不相等的,在存储时如左图所示,当两个对象的hashcode相等,但是equals()不相等,在实际中,会在同一个位置,用链式结构来保存多个对象,而HashSet访问集合元素时也是根据元素的hashCode值快速定位,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。
hash算法的功能是能保证快速查找被检索的对象,hash算法的价值在于速度,当需要查询集合中某个元素时,hash算法可以直接根据该元素的hashCode值计算出该元素的存储位置,从而快速定位该元素。
简单测试:
package com.microtao.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 测试:
* 重写hashCode和equals方法,作为判断两者是否相等
*
* @author Microtao
*
*/
class Per{
private String name;
private int age;
private String sex;
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 String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Per(String name, int age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Per [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
@Override
public int hashCode() {
return this.age * 31;
}
@Override
public boolean equals(Object obj) {
if(obj != null) {
if(obj instanceof Per) {
Per p = (Per) obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
return false;
}
}
public class HashSetTest {
public static void main(String[] args) {
Set hs = new HashSet();
hs.add(new Per("zhangsan",12,"男"));
hs.add(new Per("lisi",12,"女"));
hs.add(new Per("wangwu",12,"男"));
hs.add(new Per("zhaoliu",12,"男"));
hs.add(new Per("zhaoliu",12,"女"));
Iterator it = hs.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
运行结果:
Per [name=zhangsan, age=12, sex=男]
Per [name=lisi, age=12, sex=女]
Per [name=wangwu, age=12, sex=男]
Per [name=zhaoliu, age=12, sex=男]
上面的运行可以看出,name = zhaoliu ,age = 12有两个,但是在sex字段的值是不一样的,而在前面判断两个对象是否相等时,没有将sex字段考虑进去,所以会认为两者时一样的,所以在判断对象是否相等时,是通过计算两者的hashCode值,和equals方法进行的,这是作为Set集合判断不存重复元素的根本原因。
总结;
1、使用HashSet集合时, 首先应该知道它是无序的,其次不存在重复元素。
2、如何判断是否是重复的元素也是应该很清楚的
3、如果计算两者的hashCode值一样,但是equals不一样,也是不一样的对象,在存储时,会采用链式结构进行存储。