前几篇文章已经介绍了关于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值,将会导致性能下降。

Java集合顶层 java集合底层实现原理_HashSet底层原理

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不一样,也是不一样的对象,在存储时,会采用链式结构进行存储。