深入理解HashSet(底层是HashMap)

深入理解HashSet(底层是HashMap)首先是有一个悲伤的故事讲道理 这是面试时遇到的第一个卡壳以至于转移面试官注意力的地方 还好之前有被人指点一下加确实已经仔细研究过 HashMap 才不至于无法补救其次我 TM 惊呆了本想着回来以后好好看看 HashSet 的底层实现 结果打开源码一看的我惊呆了 nbsp wocao 怎么这么刺眼呢 你是 set 啊 你是 Collection 的子类啊 你叔叔才是 Map 啊 nbsp nbsp 你这样我心好痛啊 nbsp nbsp 冷静下来我仔细一想 S

首先是有一个悲伤的故事

讲道理,这是面试时遇到的第一个卡壳以至于转移面试官注意力的地方(……),还好之前有被人指点一下加确实已经仔细研究过HashMap,才不至于无法补救

其次我TM惊呆了

转一下dalao的博客

于是接着去看网上的dalao的博客,发现了这一篇私自转载dalao博文侵删

HashSet概述和实现

HashSet插入

当有新值加入时,底层的HashMap会判断Key值是否存在(HashMap细节请移步深入理解HashMap),如果不存在,则插入新值,同时这个插入的细节会依照HashMap插入细节;如果存在就不插入

删除

同HashMap删除原理

源码分析

盗(xue)用(xi)一下dalao 的分析代码,侵权请告之,立马删除

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { 
      static final long serialVersionUID = -L; // 底层使用HashMap来保存HashSet中所有元素。  private transient HashMap 
    
      map; 
     // 定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。  
     private 
     static 
     final Object PRESENT = 
     new Object(); 
     / * 默认的无参构造器,构造一个空的HashSet。 * * 实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。 */ 
     public 
     HashSet() { map = 
     new HashMap 
     
       (); } 
      / * 构造一个包含指定collection中的元素的新set。 * * 实际底层使用默认的加载因子0.75和足以包含指定 * collection中所有元素的初始容量来创建一个HashMap。 * @param c 其中的元素将存放在此set中的collection。 */ 
      public 
      HashSet(Collection 
       c) { map = 
      new HashMap 
      
        (Math.max(( 
       int) (c.size()/ 
       .75f) + 
       1, 
       16)); addAll(c); } 
       / * 以指定的initialCapacity和loadFactor构造一个空的HashSet。 * * 实际底层以相应的参数构造一个空的HashMap。 * @param initialCapacity 初始容量。 * @param loadFactor 加载因子。 */ 
       public 
       HashSet( 
       int initialCapacity, 
       float loadFactor) { map = 
       new HashMap 
       
         (initialCapacity, loadFactor); } 
        / * 以指定的initialCapacity构造一个空的HashSet。 * * 实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。 * @param initialCapacity 初始容量。 */ 
        public 
        HashSet( 
        int initialCapacity) { map = 
        new HashMap 
        
          (initialCapacity); } 
         / * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。 * 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。 * * 实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。 * @param initialCapacity 初始容量。 * @param loadFactor 加载因子。 * @param dummy 标记。 */ HashSet( 
         int initialCapacity, 
         float loadFactor, 
         boolean dummy) { map = 
         new LinkedHashMap 
         
           (initialCapacity, loadFactor); } 
          / * 返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。 * * 底层实际调用底层HashMap的keySet来返回所有的key。 * 可见HashSet中的元素,只是存放在了底层HashMap的key上, * value使用一个static final的Object对象标识。 * @return 对此set中元素进行迭代的Iterator。 */ 
          public Iterator 
           
           iterator() { 
           return map.keySet().iterator(); } 
           / * 返回此set中的元素的数量(set的容量)。 * * 底层实际调用HashMap的size()方法返回Entry的数量,就得到该Set中元素的个数。 * @return 此set中的元素的数量(set的容量)。 */ 
           public 
           int 
           size() { 
           return map.size(); } 
           / * 如果此set不包含任何元素,则返回true。 * * 底层实际调用HashMap的isEmpty()判断该HashSet是否为空。 * @return 如果此set不包含任何元素,则返回true。 */ 
           public 
           boolean 
           isEmpty() { 
           return map.isEmpty(); } 
           / * 如果此set包含指定元素,则返回true。 * 更确切地讲,当且仅当此set包含一个满足(o==null ? e==null : o.equals(e)) * 的e元素时,返回true。 * * 底层实际调用HashMap的containsKey判断是否包含指定key。 * @param o 在此set中的存在已得到测试的元素。 * @return 如果此set包含指定元素,则返回true。 */ 
           public 
           boolean 
           contains(Object o) { 
           return map.containsKey(o); } 
           / * 如果此set中尚未包含指定元素,则添加指定元素。 * 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2)) * 的元素e2,则向此set 添加指定的元素e。 * 如果此set已包含该元素,则该调用不更改set并返回false。 * * 底层实际将将该元素作为key放入HashMap。 * 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key * 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true), * 新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, * 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, * 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。 * @param e 将添加到此set中的元素。 * @return 如果此set尚未包含指定元素,则返回true。 */ 
           public 
           boolean 
           add(E e) { 
           return map.put(e, PRESENT)== 
           null; } 
           / * 如果指定元素存在于此set中,则将其移除。 * 更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e, * 则将其移除。如果此set已包含该元素,则返回true * (或者:如果此set因调用而发生更改,则返回true)。(一旦调用返回,则此set不再包含该元素)。 * * 底层实际调用HashMap的remove方法删除指定Entry。 * @param o 如果存在于此set中则需要将其移除的对象。 * @return 如果set包含指定元素,则返回true。 */ 
           public 
           boolean 
           remove(Object o) { 
           return map.remove(o)==PRESENT; } 
           / * 从此set中移除所有元素。此调用返回后,该set将为空。 * * 底层实际调用HashMap的clear方法清空Entry中所有元素。 */ 
           public 
           void 
           clear() { map.clear(); } 
           / * 返回此HashSet实例的浅表副本:并没有复制这些元素本身。 * * 底层实际调用HashMap的clone()方法,获取HashMap的浅表副本,并设置到HashSet中。 */ 
           public Object 
           clone() { 
           try { HashSet 
           
             newSet = (HashSet 
            
              ) 
             super.clone(); newSet.map = (HashMap 
             
               ) map.clone(); 
              return newSet; } 
              catch (CloneNotSupportedException e) { 
              throw 
              new InternalError(); } } } 
              
             
            
           
          
         
        
       
      
    

注意

  • 说白了,HashSet就是限制了功能的HashMap,所以了解HashMap的实现原理,这个HashSet自然就通
  • 对于HashSet中保存的对象,主要要正确重写equals方法和hashCode方法,以保证放入Set对象的唯一性
  • 虽说是Set是对于重复的元素不放入倒不如直接说是底层的Map直接把原值替代了(这个Set的put方法的返回值真有意思)
  • HashSet没有提供get()方法,愿意是同HashMap一样,Set内部是无序的,只能通过迭代的方式获得

说起来你可能不信

本来是打算分开写集合框架的底层分析的,直到我发现,LinkedHashSet是继承自HashSet,底层实现是LinkedHashMap。并且其初始化时直接super(......),瞬间我就觉得,Set写在一起得了

LinkedHashSet

同HashSet相比并没有实现新的功能(新的方法),只不过把HashSet中预留的构造方法启用了,因而可以实现有序插入,而这个具体的实现要去看LinkedHashMap了,我们使用时是不需要再可以去设置参数的,直接拿来用即可。

 / * The iteration ordering method for this linked hash map: true * for access-order, false for insertion-order. * * @serial */ final boolean accessOrder;

查看了LinkedHashMap的构造方法后,发现其因为继承自HashMap,所以其底层实现也是HashMap!!!(呵呵,我已经发现了……怪不得还是得主要研究HashMap啊),然后发现了LinkedHashMap调用父类构造方法初始化时,还顺便设置了变量accessOrder = false,看上面得源码可以知道,这是给了迭代器一个参数,false代表迭代时使用插入得顺序(追根溯源了,真爽)

偶然发现

查看源码时,我发现了一个奇怪的重写的方法:public Spliterator

spliterator()
,查了查资料发现叫做可分割迭代器,这个接口是为了并行遍历数据源中的元素而设计的迭代器,为了更好的发挥多核CPU的能力。 
其实这样我想起了要去关注一下集合框架中的并发安全了。

TreeSet

根据Set的这个尿性,我先猜测一波,TreeSet的底层实现是TreeMap(而且我在猜TreeMap的底层实现借助了HashMap)。一看源码,哎呦我去,还真是(呵呵,到底谁才是你爹…..心疼一波Collection,Map又不继承Collection接口)

 public TreeSet() { this(new TreeMap 
    
      ()); } 
    
  • 1
  • 2
  • 3

TreeSet特点与实现机制

 / * Constructs a new, empty tree set, sorted according to the specified * comparator. All elements inserted into the set must be mutually * comparable by the specified comparator: {@code comparator.compare(e1, * e2)} must not throw a {@code ClassCastException} for any elements * {@code e1} and {@code e2} in the set. If the user attempts to add * an element to the set that violates this constraint, the * {@code add} call will throw a {@code ClassCastException}. * * @param comparator the comparator that will be used to order this set. * If {@code null}, the {@linkplain Comparable natural * ordering} of the elements will be used. */ public TreeSet(Comparator 
    super E> comparator) { this(new TreeMap<>(comparator)); }

所以说

所以说使用Set需要注意的还是根据自己的需求选取正确的存储结构即可,而因为并没有get()方法给你使用,所以还是要用迭代器来获取想要的元素,然后本次Set深入分析到此结束,我要去再开一坑研究TreeMap了(滑稽)

小总结

转载自: https://blog.csdn.net/Sugar_Rainbow/article/details/
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/218288.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午7:37
下一篇 2026年3月18日 上午7:37


相关推荐

  • 面试之Solr&Elasticsearch[通俗易懂]

    面试之Solr&Elasticsearch[通俗易懂]面试之Solr&Elasticsearch

    2022年4月23日
    62
  • Groovy新手教程

    Groovy新手教程

    2021年12月1日
    40
  • 网页java挂挖矿_记一次服务器被植入挖矿脚本的解决过程

    网页java挂挖矿_记一次服务器被植入挖矿脚本的解决过程记一次服务器被植入挖矿脚本的解决过程删除挖矿脚本和对应的进程找出并删除对应挖矿脚本文件找出进程 pid 并且 kill 掉无法 kill 掉的是原进程的守护进程 原进程不在它也会自动关闭 所以不用管它 kill 掉后 cpu 恢复正常需要检查是否有定时任务防止挖矿脚本重新下载 我这里没有 如果有可以使用以下命令删除 crontab r 检查 docker 是否也有挖矿的容器在运行检查创建的 docker 容器我这里发现了很多

    2026年3月18日
    2
  • SpringBoot面试题及答案 110道(持续更新)

    SpringBoot面试题及答案 110道(持续更新)最新SpringBoot面试题【附答案解析】SpringBoot面试题及答案,SpringBoot最新面试题及答案,SpringBoot面试题新答案已经全部更新完了,有些答案是自己总结的,也有些答案是在网上搜集整理的。这些答案难免会存在一些错误,仅供大家参考。如果发现错误还望大家多多包涵,不吝赐教,谢谢~如果不背SpringBoot面试题的答案,肯定面试会挂!这套SpringBoot面试题大全,希望对大家有帮助哈~博主已将以下这些面试题整理成了一个面试手册,是PDF版的1、SpringBo

    2022年5月12日
    41
  • 【ubuntu修改密码】ubuntu忘记密码,修改密码[通俗易懂]

    【ubuntu修改密码】ubuntu忘记密码,修改密码[通俗易懂]ubuntu忘记密码,修改密码在启动ubuntu时,迅速按下shift键,进入grub启动菜单界面,选中高级选项,回车;选择recoverymode模式,即系统和密码恢复模式。然后按e启动编辑上下移动光标到recoverynomodeset位置,删除recoverynomodeset删除之后,在该位置添加quietsplashrwinit=/bin/bash,然后按f10按下f10后,进入编辑页面,在这里可以通过输入passwd来重置root账户密码,也可以通过输入passw

    2026年4月18日
    5
  • 扣子(coze)智能体| 用工作流一键生成可打印的制式教案

    扣子(coze)智能体| 用工作流一键生成可打印的制式教案

    2026年3月12日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号