Java中HashSet和LinkedHashSet详解

 更新时间:2023年09月04日 09:12:45   作者:给你两窝窝  
这篇文章主要介绍了Java中HashSet和LinkedHashSet详解,   HashSet是Set接口的子类,其内部采用了HashMap作为数据存储,HashSet其实就是在操作HashMap的key,HashSet是无序存储的,不能保证元素的顺序;HashSet并没有进行同步处理,因此是线程不安全的,需要的朋友可以参考下

一、HashSet介绍

HashSet是Set接口的子类,其内部采用了HashMap作为数据存储,HashSet其实就是在操作HashMap的key。

  • HashSet是无序存储的,不能保证元素的顺序;
  • HashSet并没有进行同步处理,因此是线程不安全的;
  • HashSet可以存储null元素,但只能存储一个。

二、源码解析

1、HashSet实现的接口

如下图:

观察上图:

  • AbstractSet类:该类提供了Set接口的骨架实现,通过扩展此类来实现集合的过程与通过扩展AbstractCollection实现集合的过程相同,除了此类的子类中的所有方法和构造函数都必须遵守由Set接口施加的附加约束(例如,添加方法不能允许将一个对象的多个实例添加到集合中)。
  • Set接口:继承Collection接口,添加了所有构造函数的约定以及add,equals和hashCode方法的约定
  • Serializable接口:主要用于序列化,即:能够将对象写入磁盘。与之对应的还有反序列化操作,就是将对象从磁盘中读取出来。因此如果要进行序列化和反序列化,ArrayList的实例对象就必须实现这个接口,否则在实例化的时候程序会报错(java.io.NotSerializableException)。
  • Cloneable接口:实现Cloneable接口的类能够调用clone方法,如果没有实现Cloneable接口就调用方法,就会抛出异常(java.lang.CloneNotSupportedException)。

2、HashSet中的变量

序列化ID

static final long serialVersionUID = -5024744406713321676L

底层使用HashMap来保存所有元素,确切说存储在map的key中,并使用transient关键字修饰,防止被序列化

private transient HashMap<E,Object> map

常量,构造一个虚拟的对象PRESENT,默认为map的value值(HashSet中只需要用到键,而HashMap是key-value键值对,使用PRESENT作为value的默认填充值,解决差异问题)

private static final Object PRESENT = new Object()

3、HashSet的构造方法

(1)无参构造

    public HashSet() {
        map = new HashMap<>();
    }

总结:默认的无参构造,其底层会初始化一个空的HashMap,并使用默认初始容量16和负载因子0.75。

(2)带集合参数的构造方法

    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

总结:带集合参数的构造方法,底层使用默认的负载因子0.75和足以包含指定集合中所有怨怒是的初始容量来构造一个HashMap。

(3)带初始容量的构造方法

    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

总结:以指定初始容量的HashMap构造一个HashSet。

(4) 带初始容量和负载因子的构造方法

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

总结:构造一个具有初始容量和负载因子的HashMap。

4、常用方法分析

(1)add()方法

add方法底层实际上是将该元素作为key添加到HashMap中,而PRESENT是作为默认的map的value值。

如果添加的元素不存在,则加入到map中,并返回true。如果该元素已经存在,则返回false。

map的put方法在添加key-value对时,如果新放入HashMap的Entry中key与集合中原有的Entry的key相同,则新添加的Entry的value会覆盖原来Entry的value,但是key不会改变。

因此,如果向HashSet中添加一个已经存在的元素时,新添加的集合元素不会被放入HashMap中,原有的元素也不会有任何改变,因此Set中的元素也就不会重复了

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

(2)remove()方法

如果给定的元素在HashSet中,则将其移除,其底层调用HashMap的remove()方法删除指定Entry。

    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

(3) size()方法

返回HashSet中元素的个数

    public int size() {
        return map.size();
    }

(4)isEmpty()方法

判断HashSet是否为空

    public boolean isEmpty() {
        return map.isEmpty();
    }

(5)contains()方法

如果HashSet中包含指定元素,则返回true。否则返回false

    public boolean contains(Object o) {
        return map.containsKey(o);
    }

5、HashSet与LinkedHashSet

在HashSet的源码中有这样一个构造函数:

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

可以看到该构造函数为包访问权限,不对外公开。该构造函数以指定的初始容量和负载因子构造一个新的空链表哈希集合,其实它是对LinkedHashSet的支持。

我们可以看看LinkedHashSet的继承关系:

因此,在这里我们可以简单的对LinkedHashSet进行分析,它在实现了Set接口、Cloneable接口和Serializable接口的同时,也继承了HashSet。

LinkedHashSet的特点如下:

  • LinkedHashSet 是 HashSet 的子类
  • LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  • LinkedHashSet 不允许添重复元素

来看看LinkedHashSet的构造方法

    //使用指定的初始容量和负载因子构造一个新的哈希集合
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }
    //使用指定的初始容量和负载因子构造新的哈希集合
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }
    //无参构造方法,默认容量16,负载因子0.75
    public LinkedHashSet() {
        super(16, .75f, true);
    }
    //基于集合构造一个带有指定元素的非空哈希集合
    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }

LinkedHashSet维护了一个hash表和双向链表,且通过head和tail分别指向链表的头和尾,每一个节点有before和after属性,这样可以形成双向链表。

在添加一个元素时,先求hash值,再求索引,确定该元素在table表中的位置,然后将添加的元素加入到双向链表(如果该元素已经存在,则不添加)

三、总结

1、HashSet总结

(1)基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

(2)当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。

(3)HashSet的其他操作都是基于HashMap的。

2、LinkedHashSet总结

LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承于 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。

到此这篇关于Java中HashSet和LinkedHashSet详解的文章就介绍到这了,更多相关HashSet和LinkedHashSet内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • spring boot 使用profile来分区配置的操作

    spring boot 使用profile来分区配置的操作

    这篇文章主要介绍了spring boot使用profile来分区配置的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 使用java执行定时任务示例

    使用java执行定时任务示例

    这篇文章主要介绍了使用java执行定时任务示例,需要的朋友可以参考下
    2014-04-04
  • SpringBoot 整合RabbitMq 自定义消息监听容器来实现消息批量处理

    SpringBoot 整合RabbitMq 自定义消息监听容器来实现消息批量处理

    Spring Boot中提供了默认的监听器容器,但是有时候我们需要自定义监听器容器,来满足一些特殊的需求,比如批量获取数据,这篇文章主要介绍了SpringBoot 整合RabbitMq 自定义消息监听容器来实现消息批量处理,需要的朋友可以参考下
    2023-04-04
  • Spring配置数据源的三种方式(小结)

    Spring配置数据源的三种方式(小结)

    本文主要介绍了Spring配置数据源的三种方式,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • Java通过SSM完成水果商城批发平台流程

    Java通过SSM完成水果商城批发平台流程

    这是一个使用了java+SSM开发的网上水果商城批发平台,是一个实战小练习,具有水果商城批发该有的所有功能,感兴趣的朋友快来看看吧
    2022-06-06
  • Spring Cloud Stream简单用法

    Spring Cloud Stream简单用法

    Spring cloud stream是为构建微服务消息驱动而产生的一种框架。Spring Cloud Stream基于Spring boot的基础上,可创建独立的、生产级别的Spring应用,并采用Spring Integration来连接消息中间件提供消息事件驱动,一起看看吧
    2021-07-07
  • 解决java执行cmd命令调用ffmpeg报错Concat error - No such filter ''[0,0]''问题

    解决java执行cmd命令调用ffmpeg报错Concat error - No such filter ''[0,0]

    这篇文章主要介绍了java执行cmd命令,调用ffmpeg报错Concat error - No such filter '[0,0]'解决方法,本文通过截图实例代码说明给大家介绍的非常详细,对大家的工作或学习有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • Java之接口(Interface)用法及说明

    Java之接口(Interface)用法及说明

    这段描述聚焦于Java接口的核心概念及其在编程中的应用,强调接口作为行为标准和契约的重要性,通过多实现机制增强类的扩展能力,同时减少耦合提高代码灵活性,文章通过USB接口的比喻生动解释了接口的约束与多态特性
    2026-05-05
  • SpringBoot整合SpringTask实现定时任务的流程

    SpringBoot整合SpringTask实现定时任务的流程

    这篇文章主要介绍了SpringBoot整合SpringTask实现定时任务的流程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • 零基础写Java知乎爬虫之将抓取的内容存储到本地

    零基础写Java知乎爬虫之将抓取的内容存储到本地

    上一回我们说到了如何把知乎的某些内容爬取出来,那么这一回我们就说说怎么把这些内容存储到本地吧。
    2014-11-11

最新评论