深入理解HashSet去重原理

        hello,大家好!最近小编我在重温Set集合中学会了很多,尤其是对于HashSet的去重?初学java的时候对于hashSet也只是一比带过只知道他可以去重但又不知其背后的原理,而面对将个属性值相同的对象时用hashSet依旧不能消除重复的问题也只是(以他们在地址值不一样的答案简单说服自己)。但是,在现实中我们则是以属性一样的对象为同一个对象。就好比我们身份证的ID属性,如果我们将相同的身份证ID输入电脑,电脑却以地址值不一样而判断他们是不同的人,那我们辛辛苦苦研发计算机如果是这样的话,那小编我可要说了:“要你有何用”?

下面我将以代码和运行结果的方式带你了解HashSet的去重原理

     1.  当集合类型为基本数据类型或者String类型的时候,Hashset的去重可以成功实现有以下代码可以得知,现在我先建一个集合类型为String类型的HashSet集合来进行实验:

package SetList;

import java.util.HashSet;
import java.util.Set;

public class DemoTest1 {

	public static void main(String[] args) {
		Set<String> set  =new HashSet<String>();//创建一个String类型的集合
		boolean s1 = set.add("a");  //添加两个相同的元素,并用boolean类型进行检验
		boolean s2=set.add("a");    //再添加一个相同元素
		boolean s3=	set.add("b");   //添加一个不值的元素
		
         System.out.println("s1的boolean值为:"+s1);  //输出s1
         
         System.out.println("s2的boolean值为:"+s2);  //输出s2
         
         System.out.println("s3的boolean值为:"+s3);  //输出s3
         
         System.out.println("整个set集合的值为:"+set); //输出整个集合
         
         
	}

}

运行结果如下:

 

 我们可以明显的看出s2的值为false说明a没有加入到set集合当中,但是s3的值为true说明不同于a的b却可以成功的加入到集合当中。最终的set值也只有两个,说明HashSet的去重效果显著。

接下来我们试试自定义对象再来看看:

2.在另一个包建立一个Person类:

package bean;

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;
	}


	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;
	}


	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}

 再建立一个类新建一个为类型为Person的Set集合:

package SetList;

import java.util.HashSet;

import bean.Person;

public class DemoTest2 {

	public static void main(String[] args) {
		HashSet<Person> he = new HashSet<>();//对象类集合

		he.add(new Person("张三",23));   //在hs集合中添加Person类对象

		he.add(new Person("张三",23));   //重复添加

		he.add(new Person("李四",24));   //添加李四

		he.add(new Person("李四",24));

		he.add(new Person("李四",24));

		he.add(new Person("李四",24));

		for (Person person : he) {       //遍历该集合
			System.out.println(person);
		}

		
	  }

	}

运行结果如下  : 

                                                         

 

 看到没,HashSet去重失败!来个简单的理由说服自己吧:“每个对象在内存的地址值不一样!所以被认为是不同的对象。”那接下来我们就在Person类中重写个equals()方法一一进行比对看看呗

                                                                       

package bean;

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;
	}


	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;
	}


	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
@Override
	public boolean equals(Object obj) {
		System.out.println("我执行了吗");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age ==p.age;
		
	}

}

 再看看运行台

 

依然毫无波澜,equals()方法也没有成功执行,既然是HashSet那么怎么能少了个HashCode()方法呢?来,加上:

 

package bean;

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;
	}


	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;
	}


	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
@Override
	public boolean equals(Object obj) {
		System.out.println("我执行了吗");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age ==p.age;
		
	}
@Override
	public int hashCode() {//我给加上hashCode()方法
	
		return super.hashCode();
	}

}

再次转向运行台,依旧毫无波澜(太失望了!!!)那就再改改吧

我返回一个10看看

@Override
	public boolean equals(Object obj) {
		System.out.println("我执行了吗");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age ==p.age;
		
	}
@Override
	public int hashCode() {//我给加上hashCode()方法
	
		return 10;
	}
//前面的代码和上几张图一样

再来看看运行台:

这次终于执行,原来小小的10竟然有如此魅力勾住我equals方法!

为什么呢?

爱肝代码的小编我特意去学习了相关的知识,原来在HashSet集合中,当输入两个相同属性值的对象时,程序是先经过HashCode()方法之后再去执行equals()方法的,而如果没有重写HashCode()方法的话则默认返回对象的地址值,当然两个对象的地址值是不同的,也就没有继续执行equals方法继续进一步的对象比对。当返回的值相同时就进行下一步的比对equals()方法比对。但是虽然说代码equals()方法成功执行并输出出了正确的结果,但是输出的多条“我执行了吗”可见执行了很多次该方法。

我们知道如果在集合中我们添加了很多个对象,而我们统一给定一个hashcode的返回值的话,就都会去执行equals()方法,那么效率就特别慢。那就再改一下吧:

@Override
	public boolean equals(Object obj) {
		System.out.println("我执行了吗");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age ==p.age;
		
	}
@Override
	public int hashCode() {//我给加上hashCode()方法
	
		return age;
	}
//前面的代码和上几张图一样

再次运行:

 我们可以明显感觉到执行的equals()方法次数更少了一些,但是细心的朋友可以发现如果年龄的值都是24的话那么,equals()方法执行的次数依旧很多,因此返回age依旧是一个不好的法子。我们就要使HashCode方法返回的值尽可能的不同,接下来试试这个

@Override
	public boolean equals(Object obj) {
		System.out.println("我执行了吗");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age ==p.age;
		
	}
//让hashcode的值尽量不重复
	@Override
	public int hashCode() {
		final int NUM = 38;
		return name.hashCode() * NUM +age;
	}

这种方法进一步的使hashcode的值不一样,从而进一步的优化了代码。但是当数如繁星的对象进入集合当中,这种方法依旧是效率低。没办法,我们看看eclipse官方提供的方法吧:(欢迎来到我的详解代码)

package bean;

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;
	}


	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;
	}


	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
/以下方法的执行顺序为:先执行hashcode方法再执行equals方法,当hashcode的值为一样的时候才会执行equals方法

	@Override
	public int hashCode() {//尽量是hashcode的值不一样
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}


/*
 * (non-Javadoc)
 * @see java.lang.Object#equals(java.lang.Object)
 * 为什么是31?
 * 1.31是一个质数(能够被一整除的数)
 * 2.31这个数既不大也不小(如果太大就会超过int的取值范围)(太小的话就有可能重复)
 * 3.31这个数好算,2的5次方减1,2向左移动5位
 */


	@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
	}
	

}

总结:

HashSet的原理:

当我们使用Set集合时,都是去用它去除里面的重复元素,假如我们在存储的时候对于那些对象一一进行equals()方法进行比较的话,这效率可就太低了(耗时间又耗内存),而使用hash算法的HashCode()方法,则提高了去重的效率,也使equals()方法的执行次数大大降低!

1.当我们的HashSet调用add()方法去存储对象的时候,是先调用对象的HashCode()得到一个哈希值,然后在集找是否有哈希值相同的对象。

                (1)、如果没有哈希值相同的对象就直接加入到集合当中不用再经过equals()方法进行比对

               (2)、如果哈希值相同的对象的话,就进一步用equals()方法比对去重。

2. 将我们自定义的对象存入set集合当中时:

           【1】、类中必须重写equals()和HashSet()这两个方法

            【2】、HashCode():属性相同的对象返回值必须是相同的,属性不相同的对象返回值一定不同

               【3】、equals():属性相同就返回true,属性不同就返回false;(此方法是判断是否为同一个对象终极审判)   

最后,小编谢谢你能够耐心地看完这篇文章。同时欢迎更多的java学习者来到我的评论区与我一同分享讨论,我们一起进步学习!加油!

  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值