HashSet去重原理及Set无序性
HashSet的主要特征
1.实现了Collection接口的子类:Set接口。
2.HashSet的储存是无序的,即遍历的顺序和我们添加的顺序无关。
3.HashSet底层的数据结构是哈希表。根据哈希表得出的哈希值代表该对象的储存位置
4.HashSet不能添加重复的元素,底层是基于HashMap实现的
HashSet如何去重?
Set调用 add 方法时,调用了添加对象的 hashCode方法和 equals 方法:如果Set集合中没有与该元素 hashCode 值相同的元素,则说明该元素是新元素,可以添加到 Set 集合中;如果两个元素的 hashCode 值相同,再使用 equals 方法比较,如果返回值为 true,就说明两个元素相同,新元素不会被添加到 Set 集合中;如果 hashCode 值相同,但是 equals 方法比较后,返回值为 false ,就说明两个元素不相同,新元素会被添加到 Set 集合中。
一般来说,一个类必要时会同时重写hashCode()和equals()两个方法(如果没有重写,那么就默认调用Object中的hashCode()和equals())。这也是HashSet去重机制的关键。
去重代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public class User { private String name; private String sex; private int age; public User(String name,String sex, int age) { this .name=name; this .sex=sex; this .age=age; } public String getName() { return name; } public void setName(String name) { this .name = name; } public String getSex() { return sex; } public void setSex(String sex) { this .sex = sex; } public int getAge() { return age; } public void setAge( int age) { this .age = age; } //重写toString public String toString() { return name+ "--" +sex+ "--" + "--" +age; } } |
当没有重写hashCode()和equals()时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class test { public static void main(String[] args) { User user1 = new User( "张三" , "男" , 20 ); User user2 = new User( "张三" , "男" , 20 ); Set<User> users = new HashSet<User>(); users.add(user1); users.add(user2); //遍历 Iterator<User> iterator = users.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } } |
运行结果:
1 2 | 张三--男---- 20 张三--男---- 20 |
从运行结果可以看到user1和uer2都被添加进HashSet集合中。因为我这里没有重写hashCode()和equals(),所以这里默认使用了Object类中的hashCode(),通过Object中hashCode()得出的user1和user2的哈希值是不同的,因为他们的存放地址是不一样。即直接判定这两个对象是不同的,可以同时放入HashSet集合。
如果两个对象即使存储位置不同,只要属性完全相同就不能存放进HashSet。这时需要重写hashCode()和equals()方法。
在User类中添加重写的hashCode()和equals()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public int hashCode() { int result = 17 ; result = result* 31 + this .name.hashCode(); result = result* 31 + this .sex.hashCode(); result = result* 31 + this .age; return result; } // 重写equals public boolean equals(Object obj) { if ( this == obj){ return true ; //地址相等 } if (obj == null ){ return false ; //非空性:对于任意非空引用x,x.equals(null)应该返回false。 } if (obj instanceof User) { User user = (User)obj; if ( this .name.equals(user.name)&& this .sex.equals(user.sex)&& this .age==user.age) { return true ; } } return false ; } |
运行结果:
1 | 张三--男---- 20 |
可以发现,这个时候只能加入一个user,并且是user1。(因为user1先执行add操作)通过重写了hashCode()和equals()方法,我们修改了判定规则,我们通过属性的hash值返回hashCode的结果,并且在重写后的equals()方法通过比较属性来判断是否是同一个对象。
Set无序性探索
我们经常听说List是有序且可重复的,Set是无序且不重复的。这是一个误区,这里所说的顺序有两个概念,一是按照添加的顺序排列,二是按照自然顺序a-z排列。Set并不是无序的,传统所说的Set无序指的是HashSet,它不能保证元素的添加顺序,更不能保证自然顺序,而Set的其他实现类是可以实现这两种顺序的。
1,LinkedHashset : 保证元素添加的自然顺序
2,TreeSet : 保证元素的自然顺序
通过下面一段代码来理解上述问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; public class set { public static void main(String[] args) { // Tree保证元素按照自然顺序a-z排列 Set set= new TreeSet<String>(); set.add( "aa" ); set.add( "b2" ); set.add( "cc3" ); set.add( "d4" ); set.add( "a" ); System.out.println( "TreeSet---" +set); // HashSet无序 Set hashSet= new HashSet(); hashSet.add( "aa" ); hashSet.add( "b2" ); hashSet.add( "cc3" ); hashSet.add( "d4" ); hashSet.add( "a" ); System.out.println( "HashSet---" +hashSet); //LinkedHashSet会保证元素的添加顺序 Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add( "a" ); linkedHashSet.add( "c" ); linkedHashSet.add( "b" ); linkedHashSet.add( "e" ); linkedHashSet.add( "d" ); //linkedHashSet.forEach(e-> System.out.print(e+" ")); System.out.println( "LinkedHashSet---" +linkedHashSet); } } |
输出结果:
1 2 3 | TreeSet---[a, aa, b2, cc3, d4] TreeSet元素按自然顺序排序 HashSet---[aa, b2, d4, a, cc3] HashSet元素乱序 LinkedHashSet---[a, c, b, e, d] LinkedHashSet保证元素添加顺序 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程