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 Security登录表单配置示例详解

    Spring Security登录表单配置示例详解

    这篇文章主要介绍了Spring Security登录表单配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • Springboot整合分页插件PageHelper步骤解析

    Springboot整合分页插件PageHelper步骤解析

    这篇文章主要介绍了Springboot整合分页插件PageHelper步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • 关于Java中的try-with-resources语句

    关于Java中的try-with-resources语句

    这篇文章主要介绍了关于Java中的try-with-resources语句,try-with-resources是Java中的环绕语句之一,旨在减轻开发人员释放try块中使用的资源的义务,需要的朋友可以参考下
    2023-05-05
  • 一文彻底弄懂Java中MultipartFile接口和File类

    一文彻底弄懂Java中MultipartFile接口和File类

    MultipartFile是一个接口,我们可以理解为是Spring 给我们绑定的一个在使用文件上传等时简便实现的口子,这篇文章主要给大家介绍了关于如何通过一文彻底弄懂Java中MultipartFile接口和File类的相关资料,需要的朋友可以参考下
    2023-11-11
  • Java基于面向对象实现一个战士小游戏

    Java基于面向对象实现一个战士小游戏

    这篇文章主要为大家详细介绍了Java如何基于面向对象实现一个战士小游戏,文中的示例代码讲解详细,感兴趣的小伙伴可以动手尝试一下
    2022-07-07
  • java 四舍五入保留小数的实现方法

    java 四舍五入保留小数的实现方法

    下面小编就为大家带来一篇java 四舍五入保留小数的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • 一文搞懂Spring中的Bean作用域

    一文搞懂Spring中的Bean作用域

    scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象,这篇文章主要介绍了Spring中的Bean作用域,需要的朋友可以参考下
    2022-06-06
  • maven项目如何依赖自定jar包

    maven项目如何依赖自定jar包

    这篇文章主要介绍了maven项目如何依赖自定jar包,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Maven导入依赖时爆红的几种解决方法

    Maven导入依赖时爆红的几种解决方法

    使用idea建立maven项目,maven导入依赖报红,本文主要介绍了Maven导入依赖时爆红的几种解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • Java利用Request请求如何获取IP地址对应的省份、城市详解

    Java利用Request请求如何获取IP地址对应的省份、城市详解

    之前已经给大家介绍了关于Java用Request请求获取IP地址的相关内容,那么下面这篇文章将给大家进入深入的介绍,关于Java利用Request请求如何获取IP地址对应省份、城市的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-10-10

最新评论