HashSet类之唯一特性

HashSet类之唯一特性

一、Set集合概述

一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1e2,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。

Set集合实现了Collection接口,但没有更多新的方法,只是在所有构造方法以及 add、equals 和 hashCode 方法的协定上,Set 接口还加入了其他规定,这些规定超出了从 Collection 接口所继承的内容。这些规定是为了实现让Set集合不包含重复元素,所以我们值需要研究Set集合是如何保持元素唯一特性即可。

二、Set集合特点

无索引,不可以重复,无序(存取不一致)

public static void demo1() {
	HashSet<String> hs = new HashSet<>();	        //创建HashSet对象
	boolean b1 = hs.add("a");
	boolean b2 = hs.add("a");			//当向set集合中存储重复元素的时候返回为false
	hs.add("b");
	hs.add("c");
	hs.add("d");
	System.out.println(hs);				//HashSet的继承体系中有重写toString方法
	System.out.println(b1);                         //true
	System.out.println(b2);                         //false
		
	for (String string : hs) {			//只要能用迭代器迭代的,就可以使用增强for循环遍历
		System.out.println(string);
	}
}

三、HashSet集合概述

此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。

private transient HashMap<E,Object> map;

注意,此实现不是同步的。

四、HashSet集合保持唯一特性

由于Set集合是不包含重复值的,如果我们在存储每一个元素时都是用equals()方法进行比较,则效率较低。HashSet集合采用Hash算法提高了去重复的效率,降低了equals()方法的使用频率,具体比较如下:

当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该值确定对象在HashSet中的存储位置。

在Hash集合中,不能同时存放两个相等的元素,而判断两个元素相等的方法如下:

  1. ​​​​如果hashCode值不相同,说明是一个新元素,则存入集合中。
  2. 如果hashCode码值相同,但是equles()方法判断不相等,说明是一个新元素,,则存入集合中。
  3. 如果hashCode码值相同,且equles()方法判断相等,说明元素已经存在,则不存入集合;

对于hashCode()和equles()方法,我在(https://blog.csdn.net/qq_40298054/article/details/83622837)一文中作了详细介绍。

例如:对象相等,hashCode返回值相等;对象不相等,hashCode返回值不一定不相等,如果有兴趣的话可以进行阅读。

 所有由上可见,类中的hashCode()和equles()方法十分重要,如果需要提高性能的话,那么优化hashCode()算法以减少equles()进行比较的次数对于优化性能是十分重要的(即在创建的Hash表中减少冲突)。

案例:

public class Person{
	private String name;
	private int age;
	public Person() {
		super();
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public boolean equals(Object obj) {
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age == p.age;
	}
	@Override
	public int hashCode() {
		final int NUM = 38;
                //String类重写了hashCode()方法,我们可以直接使用
                //定义一个参数是为了尽量减少hashCode值重复的几率
		return name.hashCode() * NUM + age;    
	}
}

其实在开发中,我们不需要费力重写这两个方法,编译器已经为我们重写完成,我们只需要直接选择生成即可。

/*
 * 为什么是31?
 * 1,31是一个质数,质数是能被1和自己本身整除的数
 * 2,31这个数既不大也不小
 * 3,31这个数好算,2的五次方-1,2向左移动5位
*/
@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + age;
	result = prime * result + ((name == null) ? 0 : name.hashCode());
	return result;
	}
@Override
public boolean equals(Object obj) {
	if (this == obj)				//调用的对象和传入的对象是同一个对象
		return true;				//直接返回true
	if (obj == null)				//传入的对象为null
		return false;				//返回false
	if (getClass() != obj.getClass())		//判断两个对象对应的字节码文件是否是同一个字节码
		return false;				//如果不是直接返回false
	Person other = (Person) obj;			//向下转型
	if (age != other.age)				//调用对象的年龄不等于传入对象的年龄
		return false;			        //返回false
	if (name == null) {			        //调用对象的姓名为null
		if (other.name != null)			//传入对象的姓名不为null
			return false;			//返回false
	} else if (!name.equals(other.name))            //调用对象的姓名不等于传入对象的姓名
		return false;				//返回false
	return true;					//返回true
}

我们可以看出,系统为我们生成的两个方法十分复杂,这是因为为了程序的健壮性考虑。程序中包含的判断越多,则出现故障的概率越小。我们知道印度的计算机行业比较发达,就是因为他们编写程序时考虑的比较周全,出现的错误较少。所以我们在以后编写代码时尽量注意这一点。

五、LinkedHashSet

具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。

此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表(底层链表实现)。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代(是set集合中唯一一个能保证怎么存就怎么取的集合对象)。注意,插入顺序 受在 set 中重新插入的 元素的影响。

因为该类是HashSet的子类,所以也是保证元素唯一的,与HashSet的原理一样

猜你喜欢

转载自blog.csdn.net/qq_40298054/article/details/84966611