JavaHashSet使用全解析
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《HashSet在Java中的使用详解》,聊聊,我们一起来看看吧!
HashSet基于哈希表实现,不保证顺序但确保元素唯一,通过hashCode()和equals()判断重复,允许一个null元素;在添加、删除、查找操作中具有平均O(1)时间复杂度,适用于去重场景。创建时可指定初始容量以优化性能,需注意元素的hashCode()和equals()方法必须正确重写,尤其是自定义对象;存储对象的关键字段应保持不变,避免因哈希码变化导致元素“丢失”。HashSet非线程安全,多线程环境下需使用Collections.synchronizedSet或ConcurrentHashMap.newKeySet()。与ArrayList相比,HashSet不允许重复且查询效率高,但不支持索引访问;而ArrayList有序、允许重复,适合按索引操作的场景。TreeSet则基于红黑树,保证元素排序,插入和查找时间为O(log n),适用于需要有序且无重复元素的场景。实际选择依据需求:无需顺序仅去重用HashSet,需插入顺序和索引访问用ArrayList,需排序去重用TreeSet。

Java中的HashSet是一种基于哈希表的集合实现,它不保证元素的顺序,但能确保集合中没有重复的元素。其核心用途就是高效地存储和检索不重复的对象,就像我们整理文件时,希望能把重复的文档筛选掉,只保留一份。它的内部机制使得在添加、删除或检查元素是否存在时,平均时间复杂度能达到O(1),这在处理大量数据时效率非常高。
解决方案
使用HashSet通常从创建实例开始,然后通过其提供的方法进行操作。
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
// 1. 创建一个HashSet实例
// 我们可以指定泛型,例如存储字符串
Set<String> uniqueNames = new HashSet<>();
// 2. 添加元素
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Charlie");
uniqueNames.add("Alice"); // 尝试添加重复元素,HashSet会忽略
System.out.println("添加元素后: " + uniqueNames); // 输出可能无序,但Alice只出现一次
// 3. 检查元素是否存在
boolean containsBob = uniqueNames.contains("Bob");
System.out.println("是否包含Bob? " + containsBob);
boolean containsDavid = uniqueNames.contains("David");
System.out.println("是否包含David? " + containsDavid);
// 4. 获取集合大小
int size = uniqueNames.size();
System.out.println("集合大小: " + size);
// 5. 移除元素
uniqueNames.remove("Bob");
System.out.println("移除Bob后: " + uniqueNames);
// 6. 遍历HashSet
System.out.print("遍历集合: ");
for (String name : uniqueNames) {
System.out.print(name + " ");
}
System.out.println();
// 7. 清空集合
uniqueNames.clear();
System.out.println("清空后: " + uniqueNames);
System.out.println("清空后集合大小: " + uniqueNames.size());
// 存储自定义对象
Set<Person> uniquePeople = new HashSet<>();
uniquePeople.add(new Person("Alice", 30));
uniquePeople.add(new Person("Bob", 25));
uniquePeople.add(new Person("Alice", 30)); // 如果Person类没有正确重写hashCode和equals,这会被认为是不同的对象
System.out.println("自定义对象集合: " + uniquePeople);
// 为了让自定义对象正确去重,Person类需要重写hashCode()和equals()方法
// 见下面的副标题讨论
}
}
// 示例自定义类,用于演示HashSet对自定义对象的处理
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 重写hashCode()和equals()是HashSet正确处理自定义对象去重的关键
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}HashSet如何判断元素重复?null元素可以加入吗?
HashSet判断元素是否重复,依赖的是两个非常关键的方法:hashCode()和equals()。这是Java对象契约的核心部分,对于理解HashSet的去重机制至关重要。当我第一次接触到这里时,也花了一些时间去消化,因为这不像表面看起来那么简单。
当你尝试向HashSet中添加一个元素时,HashSet会做两步检查:
- 计算哈希码: 它会先调用待添加元素的
hashCode()方法,得到一个整数哈希码。这个哈希码决定了元素在底层哈希表(通常是一个数组)中的存储位置。如果两个对象的hashCode()值不同,那么它们几乎肯定被认为是不同的对象,会存储在不同的位置。 - 调用equals(): 如果两个元素的
hashCode()值相同(这可能意味着它们是同一个对象,也可能是哈希冲突),HashSet会进一步调用它们的equals()方法进行比较。只有当equals()方法返回true时,HashSet才认为这两个元素是重复的,并拒绝添加新元素。
所以,如果你的自定义类需要正确地在HashSet中去重,就必须同时重写hashCode()和equals()方法。只重写一个会导致不可预测的行为,甚至可能出现重复元素。比如,如果只重写equals(),不重写hashCode(),那么两个逻辑上相等的对象可能会有不同的哈希码,从而被存储在不同的位置,HashSet就无法去重了。
至于null元素,HashSet是允许存储一个null元素的。在HashSet内部,null元素的hashCode()被定义为0。当尝试添加第二个null时,因为其hashCode()也是0,并且null.equals(null)(当然,实际是内部处理,不会抛出NullPointerException)被认为是true,所以第二个null会被忽略。这在某些场景下很方便,比如你想统计一组数据中所有唯一的非空值,以及是否存在一个null值。
使用HashSet时需要注意哪些性能或设计上的考量?
在使用HashSet时,除了正确实现hashCode()和equals()外,还有一些性能和设计上的考量,它们能显著影响你的应用程序表现。
初始容量和负载因子:
HashSet的底层是HashMap,它有一个初始容量(默认是16)和一个负载因子(默认是0.75)。当集合中的元素数量达到初始容量 * 负载因子时,HashSet就会进行“扩容”(rehashing),创建一个更大的哈希表,并将所有现有元素重新计算哈希码并放到新表中。这个过程是比较耗时的。- 初始容量: 如果你预估
HashSet会存储大量元素,最好在创建时指定一个较大的初始容量,例如new HashSet<>(1000)。这样可以减少扩容的次数,提升性能。但也不能设置过大,否则会浪费内存。 - 负载因子: 负载因子决定了哈希表在何时扩容。较低的负载因子(比如0.5)意味着更频繁的扩容,但哈希冲突更少,查找速度可能更快;较高的负载因子(比如0.9)意味着扩容不频繁,但哈希冲突可能更多,查找速度可能变慢。通常默认值0.75是一个不错的平衡点,但在极端性能要求的场景下,可以考虑调整。
- 初始容量: 如果你预估
元素的不可变性: 这是一个非常重要的设计原则。一旦一个对象被添加到
HashSet中,它用来计算hashCode()和equals()的字段就不应该再改变。如果这些字段改变了,那么该元素的hashCode()值也可能改变。这会导致什么问题呢?当你想通过remove()或contains()方法查找这个元素时,HashSet会根据它当前的hashCode()去查找,而这个hashCode()可能已经和它被添加时存储的哈希码不一致了。结果就是,你可能找不到它,或者无法正确移除它,即使它还在集合中,但已经“失踪”了。 所以,理想情况下,存储在HashSet中的对象应该是不可变的,或者至少是那些影响hashCode()和equals()的字段是不可变的。线程安全性:
HashSet不是线程安全的。这意味着如果多个线程同时对一个HashSet进行添加、删除或修改操作,可能会导致数据不一致或产生意外行为。如果你的应用场景涉及多线程并发访问,你需要采取措施:- 外部同步: 使用
Collections.synchronizedSet(new HashSet<>())来创建一个线程安全的Set。 - 使用并发集合: 在Java 8及更高版本中,
ConcurrentHashMap.newKeySet()提供了一个高效的线程安全Set实现,它基于ConcurrentHashMap。
- 外部同步: 使用
理解这些考量能帮助我们写出更健壮、性能更好的代码,避免一些难以调试的并发问题或者性能瓶颈。
HashSet与ArrayList、TreeSet有什么区别,在实际开发中如何选择?
在Java集合框架中,HashSet、ArrayList和TreeSet是三种非常常用但特性迥异的集合类型。了解它们的区别以及何时选择哪一个,是每个Java开发者都需要掌握的技能。
HashSet(基于哈希表)- 特性: 不保证元素的顺序,不允许有重复元素。提供平均O(1)的添加、删除和查找操作。
- 内部机制: 依赖于元素的
hashCode()和equals()方法来确定唯一性和存储位置。 - 选择场景: 当你只需要存储一组不重复的元素,并且对元素的顺序没有要求时,
HashSet是最佳选择。例如,去重一个列表中的邮箱地址,或者快速检查一个用户ID是否已经存在于某个列表中。
ArrayList(基于动态数组)- 特性: 保持元素的插入顺序,允许有重复元素。支持通过索引进行快速访问(O(1)),但添加或删除中间元素时可能需要移动大量元素(O(n))。查找元素(
contains)也是O(n)。 - 内部机制: 底层是一个可变大小的数组。
- 选择场景: 当你需要一个有序的元素列表,可以包含重复项,并且经常需要通过索引访问元素时,
ArrayList是理想选择。例如,存储用户最近浏览的商品列表,或者一个任务队列。
- 特性: 保持元素的插入顺序,允许有重复元素。支持通过索引进行快速访问(O(1)),但添加或删除中间元素时可能需要移动大量元素(O(n))。查找元素(
TreeSet(基于红黑树)- 特性: 保证元素的自然排序(如果元素实现了
Comparable接口)或自定义排序(通过Comparator),不允许有重复元素。提供O(log n)的添加、删除和查找操作。 - 内部机制: 底层是一个红黑树数据结构,保持元素的有序性。
- 选择场景: 当你既需要存储不重复的元素,又需要这些元素保持某种排序时,
TreeSet是首选。例如,存储一个班级的学生成绩,并希望它们总是按分数高低排序;或者存储一个日志事件列表,并按时间戳排序。
- 特性: 保证元素的自然排序(如果元素实现了
总结选择策略:
- 需要去重且对顺序无要求? 选
HashSet。追求极致的查找、添加、删除效率。 - 需要保持插入顺序,允许重复,且常按索引访问? 选
ArrayList。 - 需要去重且元素需要保持排序? 选
TreeSet。虽然性能不如HashSet,但提供了有序性。
在实际开发中,我通常会先考虑业务需求对“重复”和“顺序”的严格程度。如果只是简单去重,HashSet几乎是我的第一选择。如果需要维护一个历史记录或者展示一个序列,ArrayList则更合适。而当数据需要天然有序时,TreeSet的价值就体现出来了。比如,在一个推荐系统中,如果需要存储用户已经看过的电影ID,并快速判断某部电影是否已推荐过,HashSet就非常高效。如果需要展示用户最近看过的10部电影,ArrayList更合适。如果需要一个排行榜,显示前N名玩家,TreeSet就能轻松搞定排序和去重。
今天关于《JavaHashSet使用全解析》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于哈希表,hashset,hashcode(),去重,equals()的内容请关注golang学习网公众号!
Symfony验证错误转数组技巧
- 上一篇
- Symfony验证错误转数组技巧
- 下一篇
- Java线程优先级设置全解析
-
- 文章 · java教程 | 28分钟前 |
- Java单例模式详解与实现技巧
- 425浏览 收藏
-
- 文章 · java教程 | 34分钟前 |
- 读写锁特性与使用场景详解
- 471浏览 收藏
-
- 文章 · java教程 | 34分钟前 |
- Java注解使用技巧与自定义方法
- 385浏览 收藏
-
- 文章 · java教程 | 46分钟前 |
- Java方法重载与可变参数解析
- 188浏览 收藏
-
- 文章 · java教程 | 54分钟前 |
- 默认方法在Java中的优势有哪些
- 148浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- JavaResultSet结果集常用方法详解
- 106浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- JavaFuture异步结果获取方法详解
- 139浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java并发计数器安全更新技巧
- 225浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Windows安装Java详细教程
- 301浏览 收藏
-
- 文章 · java教程 | 1小时前 | 多线程 数组 cas AtomicReferenceArray 原子更新
- Java原子数组高效更新方法解析
- 244浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3177次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3389次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3418次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4523次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3797次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

