Java:HashSet判断添加元素不可重复的底层原理!

前言

Set接口的实现类HashSet的特点是无序不可重复。
底层实现:
JDK 8.0 之前,数组 + 链表
JDK 8.0 及之后,数组 + 链表 + 红黑树

那么向HashSet集合中添加元素时,它是如何判断该元素是否重复呢?

下面用代码举例来演示其原理。

代码实现

首先,创建一个实体类User:

public class User {
	private String username;
	private String password;
	public User() {
	}
	public User(String username, String password) {
		this.username = username;
		this.password = password;
	}
	public String getUserName() {
		return username;
	}
	public void setUserName(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((username == null) ? 0 : username.hashCode());
		result = prime * result + ((password == null) ? 0 : password.hashCode());

		// 验证调用了hashCode并获取哈希值
		System.out.println("------" + result);

		return result;
	}
	@Override
	public boolean equals(Object obj) {

		// 验证是否调用了equals方法
		System.out.println("------ equals方法");

		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (username == null) {
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		if (password == null) {
			if (other.password != null)
				return false;
		} else if (!password.equals(other.password))
			return false;
		return true;
	}
	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password + "]";
	}

}

在上面的 hashCode() 和 equals() 方法中,分别做了标记,如果调用了这两个方法,就会在控制台打印出对应的标记,这样我们就可以知道 HashSet 在添加用户时判断重复的原理。

下面,创建HashSet集合,并添加元素(包括重复元素):

public class HashSetDemo {
	public static void main(String[] args) {

		Set<User> users = new HashSet<>();
		User u1 = new User("张三", "123456");
		User u2 = new User("李四", "654321");
		User u3 = new User("张三", "123456");

		users.add(u1);
		users.add(u2);
		users.add(u3);

		users.forEach(System.out::println);
	}

}

运行结果如下:

------1474597979
------1622625361
------1474597979
------ equals方法
User [username=李四, password=654321]
User [username=张三, password=123456]

从打印结果可以看出,前三行的三串数字分别是添加的三个元素对应的哈希值(hashCode值),说明 Set 集合通过调用 hashCode() 方法获取哈希值来初步判断当前元素是否与之前元素重复(可能产生误差,但一般几率非常非常小)。然后发现第三个元素与第一个元素的哈希值相等,在调用 equals() 方法来进一步精确判断两个元素是否相同,相同则判定为重复,所以最终结果没有重复元素。

总结

通过以上的过程发现 HashSet 集合不可重复的原理:

当第一个元素添加到 Set 集合中时,会优先调用该对象的 hashCode() 方法得到哈希值,根据这个哈希值来决定该对象存放在集合的哪个位置。
当第二个元素添加到 Set 集合中时,会继续调用该对象的 hashCode() 方法得到哈希值,此时会有两种情况:

  1. 哈希值不同,说明是不同的对象,则根据这个哈希值来决定新元素存放的位置。
  2. 哈希值相同,此时还不能说明是相同的对象,所以会继续调用 equals() 方法。
    如果 equals() 方法返回 true,说明是相同对象,则该元素将不被添加到集合中;如果 equals() 方法返回 false,说明这两个对象虽然哈希值一样,但是是不同的对象,则在该哈希值对应的位置产生链表。

补充

JDK 8.0 之前,只要产生碰撞,就在链表上加一个元素,随着元素增多,HashSet 的性能降低。
JDK 8.0 及之后,当碰撞阈值达到 8 时,自动把链表转换成红黑树,目的是为了提高 HashSet 的性能。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

toollong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值