前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详细解读 Java中的HashSet

详细解读 Java中的HashSet

作者头像
Java极客技术
发布2024-07-31 17:28:42
860
发布2024-07-31 17:28:42
举报
文章被收录于专栏:Java极客技术

每天早上八点,准时推送干货

在Java中有各种的数据结构,有数组,链表,集合等等,我们也都经常使用,但是很多在写业务代码的时候,很少去看这个源码问题,所以我们今天来看看这个关于Java 中的一个集合,也就是 HashSet。

Java 中的HashSet

Java中的HashSet是Java集合框架(Java Collections Framework)的一部分,它实现了Set接口。HashSet存储的元素是不重复的,并且它不保证集合的迭代顺序。HashSet允许存储null元素,但最多只能有一个null元素,因为集合中的元素是根据它们的hashCode()方法的返回值来存储的,并且如果两个元素的hashCode()值相同,那么它们的equals()方法也会被调用以确定它们是否相等。

至于内部实现,我们来看一下:

HashSet实际上是基于HashMap实现的,它使用HashMap来存储元素。HashSet中的每个元素都存储为HashMap中的一个键(key),而对应的值(value)则是一个固定的对象(在Java 8及更高版本中,这个对象是一个名为PRESENT的静态常量,而在Java 7及更早版本中,它通常是一个Object类型的空值,如null或新创建的Object()实例)。

HashSet的源码分析

继承与实现

HashSet类继承自AbstractSet类,并实现了Set、Cloneable和java.io.Serializable接口。这意味着HashSet是一个集合,支持克隆和序列化。

代码语言:javascript
复制
  public class HashSet<E>  
    extends AbstractSet<E>  
    implements Set<E>, Cloneable, java.io.Serializable

重要属性

HashSet中最重要的属性是一个HashMap,用于存储HashSet中的元素。HashMap的键是HashSet中的元素,而所有的键都映射到同一个虚拟的值(PRESENT),这个值是一个静态常量,用于占位。

代码语言:javascript
复制
// 使用HashMap来存储HashSet的元素  
private transient HashMap<E,Object> map;  
// HashMap中所有键对应的虚拟值  
private static final Object PRESENT = new Object();

构造方法

HashSet提供了多种构造方法,包括无参构造、带初始容量的构造、带初始容量和加载因子的构造,以及通过现有集合构造的构造方法。

  • 无参构造:创建一个空的HashSet,其内部的HashMap具有默认的初始容量(16)和加载因子(0.75)。
  • 带初始容量的构造:创建一个空的HashSet,其内部的HashMap具有指定的初始容量和默认的加载因子(0.75)。
  • 带初始容量和加载因子的构造:创建一个空的HashSet,其内部的HashMap具有指定的初始容量和指定的加载因子。
  • 通过现有集合构造:创建一个包含指定集合中所有元素的新集合,其内部的HashMap具有默认的加载因子(0.75)和足够的初始容量来包含集合中的元素。

主要方法

  • add(E e):向HashSet中添加一个元素。如果元素不存在,则将其添加到HashMap中,并返回true;如果元素已存在,则不执行任何操作并返回false。
  • remove(Object o):从HashSet中移除一个元素。如果元素存在,则将其从HashMap中移除并返回true;如果元素不存在,则返回false。
  • contains(Object o):检查HashSet中是否包含指定的元素。如果包含,则返回true;否则返回false。

扩容机制

当HashMap中的元素数量超过其容量和加载因子的乘积时(即达到阈值),HashMap会进行扩容。扩容操作会创建一个新的数组,并将旧数组中的元素重新计算哈希值后存储到新数组中。HashSet的扩容机制依赖于其内部HashMap的扩容机制。

HashSet 的存储机制

基于哈希表:HashSet 内部维护了一个哈希表(HashMap 的实例),用于存储集合中的元素。在 HashSet 中,每个元素实际上都作为 HashMap 的一个键(key)存储,而对应的值(value)则是一个固定的对象(在 Java 8 及以后版本中,这个固定对象是一个 PRESENT 常量,它是一个 Object 类型的静态常量,作为 HashMap 的值存在)。

哈希冲突:由于哈希表的大小是有限的,多个键可能通过哈希函数映射到哈希表的同一个位置,这种现象称为哈希冲突。HashSet(通过其内部的 HashMap)使用链表或红黑树(在 Java 8 及更高版本中,当链表长度超过一定阈值时,链表会转换为红黑树以提高查找效率)来解决哈希冲突。

自定义对象的处理

当在HashSet中存储自定义对象时,需要重写这些对象的hashCode()和equals()方法。这是因为HashSet(通过其内部的HashMap)使用这两个方法来检查元素的相等性和确定元素的哈希码。如果这两个方法没有被正确重写,那么HashSet可能无法正确地存储和比较自定义对象。

线程安全

HashSet不是线程安全的。如果在多线程环境下使用,需要外部同步或使用其他并发集合,如ConcurrentHashMap的键集合视图(尽管这不是HashSet,但提供了一种线程安全的集合实现方式)。然而,Java还提供了Collections.synchronizedSet方法来将任何Set包装成一个线程安全的Set,但这通常不是最高效的并发解决方案。

HashSet和HashMap的对比

存储方式不同:

  • HashSet:存储的是不重复的元素集合,这些元素可以是任意类型的对象。HashSet实际上是通过HashMap来实现的,它只使用了HashMap的键部分,而所有的键都映射到同一个虚拟的值(通常是null或某个特定的对象,如PRESENT)。
  • HashMap:存储的是键值对(Key-Value Pair),其中键是唯一的,而值可以重复。HashMap允许你根据键来快速查找、更新或删除对应的值。

实现接口不同:

  • HashSet:实现了Set接口,继承自AbstractSet类。
  • HashMap:实现了Map接口,继承自AbstractMap类。

存储特性:

  • HashSet:
    • 不允许存储重复的元素。
    • 不保证元素的迭代顺序。
    • 允许使用null元素。
  • HashMap:
    • 键(Key)是唯一的,值(Value)可以重复。
    • 允许使用null键和null值(但最多只能有一个null键)。
    • 提供了基于键的快速查找、插入和删除操作。

底层数据结构:

  • HashSet:底层实际上是一个HashMap实例,它使用哈希表来存储元素。哈希表是一个无序的数据结构,通过哈希函数将元素映射到数组的某个位置。
  • HashMap:同样使用哈希表来存储键值对。每个键值对都通过哈希函数计算出一个哈希码,然后根据这个哈希码将键值对存储在数组的某个位置。如果发生哈希冲突(即不同的键计算出相同的哈希码),则通过链表或红黑树(在Java 8及更高版本中)来解决。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java极客技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java 中的HashSet
    • HashSet的源码分析
      • HashSet 的存储机制
        • 自定义对象的处理
          • 线程安全
          • HashSet和HashMap的对比
          相关产品与服务
          对象存储
          对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档