当前位置: 首页 > 后端技术 > Java

面试官:HashSet的实现原理是什么?底层数据结构是什么?被问,.

时间:2023-04-01 23:37:04 Java

来源:https://www.cnblogs.com/LiaHo...一、HashSet概述HashSet是Java集合Set的一个实现类,Set是一个接口。除了HashSet,它的实现类还有TreeSet,并且继承了Collection,HashSet集合是非常常用的,也是程序员面试时经常被问到的一个知识点。下面是结构图publicclassHashSetextendsAbstractSetimplementsSet,Cloneable,java.io.Serializable{}2.HashSet构造HashSet有几种重载的构造方法,我们来看看privatetransientHashMapmap;//默认构造函数publicHashSet(){map=newHashMap<>();}//将传入的集合添加到HashSet构造函数中publicHashSet(Collectionc){map=newHashMap<>(Math.max((int)(c.size()/.75f)+1,16));addAll(c);}//指定初始容量和加载因子的构造函数publicHashSet(intinitialCapacity,floatloadFactor){map=newHashMap<>(initialCapacity,loadFactor);}//只指定初始容量(加载factordefaultsto0.75)publicHashSet(intinitialCapacity){map=newHashMap<>(initialCapacity);}通过上面的源码我们发现HashSet是TM是一家皮包公司,是对外工作的,当工作接收到,直接丢给HashMap处理。因为底层是通过HashMap实现的,这里简单提一下:HashMap的数据存储是通过数组+链表/红黑树实现的,大概的存储过程就是通过hash计算数组中的存储位置功能。现在判断key是否相同,相同则覆盖,不相同则放入元素对应的链表。如果链表长度大于8,则转为红黑树。如果容量不够,就需要扩容(注:这只是一般流程)。如果你对HashMap的原理不是很清楚,可以先了解一下。收藏系列教程:https://www.javastack.cn/cate...三、add方法HashSet的add方法是通过HashMap的put方法实现的,但是HashMap是key-value键值对,而HashSet是一个集合。那么它是如何存储的呢?我们看一下源码privatestaticfinalObjectPRESENT=newObject();publicbooleanadd(Ee){returnmap.put(e,PRESENT)==null;}查看源码我们知道,HashSet添加的元素存储在HashMap的key位置,value取默认常量PRESENT,它是一个空对象。4.remove方法HashSet的remove方法由HashMap的remove方法实现//HashSet的remove方法publicbooleanremove(Objecto){returnmap.remove(o)==PRESENT;}//map的remove方法publicVremove(Objectkey){节点e;//通过hash(key)找到元素在数组中的位置,然后调用removeNode方法删除return(e=removeNode(hash(key),key,null,false,true))==null?null:e.value;}/****/finalNoderemoveNode(inthash,Objectkey,Objectvalue,booleanmatchValue,booleanmovable){Node[]tab;节点p;intn,指数;//Step1.需要先找到key对应的Node的具体位置,先用(n-1)&hash找到数组A节点对应位置的第一个索引if((tab=table)!=null&&(n=tab.length)>0&&(p=tab[index=(n-1)&hash])!=null){Nodenode=null,e;Kk;v;//1.1如果这个节点刚好有相同的key值,运气好,找到了if(p.hash==hash&&((k=p.key)==key||(key!=null&&key.equals(k))))节点=p;/***1.2运气不好,虽然在数组中找到的Node的hash相同,但是key值不同,显然是错误的,需要遍历继续*往下看;*/elseif((e=p.next)!=null){//1.2.1如果是TreeNode类型,说明HashMap当前是通过数组+红黑树存储的。遍历红黑树找到对应的节点if(pinstanceofTreeNode)node=((TreeNode)p).getTreeNode(hash,key);else{//1.2.2如果是链表,遍历链表找到对应的节点do{if(e.hash==hash&&((k=e.key)==key||(key!=null&&key.equals(k)))){node=e;休息;}p=e;}while((e=e.next)!=null);}}//我们通过前面的第1步找到了对应的Node,现在我们需要删除它if(node!=null&&(!matchValue||(v=node.value)==value||(value!=null&&value.equals(v)))){/***如果是TreeNode类型,删除方法是通过删除红黑树节点来实现的。详情请参考【TreeMap原理实现*及常用方法】*/if(nodeinstanceofTreeNode)((TreeNode)node)。removeTreeNode(这个,选项卡,可移动);/***在链表的情况下,当找到的节点是数组hash位置的第一个元素,那么删除该元素后,直接将引用指向数组的第一个位置*下一个链表的数量就够了*/elseif(node==p)tab[index]=node.next;/***如果查到的节点本来就是链表上的节点,也简单,next待删除节点的前一个节点指向待删除节点的*next,隔离节点即可被删除*/elsep.next=node.next;++模数;-尺寸;//删除后可能会有存储结构调整,请参考【如何保证LinkedHashMap的顺序】中remove方法afterNodeRemoval(node);返回节点;}}returnnull;}RemoveTreeNode方法可以参考TreeMap的实现原理和常用方法5.将HashSet作为集合进行遍历,遍历的方法有很多种,比如普通的for循环,增强的for循环,迭代器,一起来看看在迭代器遍历时publicstaticvoidmain(String[]args){HashSetsetString=newHash设置<>();setString.add("星期一");setString.add("星期二");setString.add("星期三");setString.add("星期四");setString.add("星期五");迭代器it=setString.iterator();while(it.hasNext()){System.out.println(it.next());}}打印出来的结果是什么?不出所料,周二、周三、周四、周五、周一,HashSet是通过HashMap实现的,HashMap是通过hash(key)来确定存储位置的,没有存储顺序,所以HashSet遍历的元素没有按照Sequence插入6.Total按照我之前的计划,每个主要的内容都要单独写,比如集合ArrayList、LinkedList、HashMap、TreeMap等。然而,当我在写这篇关于HashSet的文章时,发现经过前面对HashMap的讲解,其实很简单。HashSet是一家皮包公司,在HashMap外加了一个壳。那么LinkedHashSet是不是只是在LinkedHashMap外面加了一个壳?那么TreeSet是不是在TreeMap外面加了一个壳呢?我们来验证一下。我们先看一下LinkedHashSet的初始结构图。前面提到LinkedHashSet是HashSet的子类。我们看源码publicclassLinkedHashSetextendsHashSetimplementsSet,Cloneable,java.io。Serializable{publicLinkedHashSet(intinitialCapacity,floatloadFactor){super(initialCapacity,loadFactor,true);}publicLinkedHashSet(intinitialCapacity){super(initialCapacity,.75f,true);}publicLinkedHashSet(){super(16,.75f,true);}publicLinkedHashSet(Collectionc){super(Math.max(2*c.size(),11),.75f,true);添加所有(c);}publicSpliteratorspliterator(){returnSpliterators.spliterator(this,Spliterator.DISTINCT|Spliterator.ORDERED);}}以上就是LinkedHashSet的所有代码,是不是觉得智商被否定了,这个基本没什么,构造函数也调用了父类,下面是父类HashSet对此的构造方法是HashSet(intinitialCapacity,floatloadFactor,booleandummy){map=newLinkedHashMap<>(initialCapacity,loadFactor);}也可以看到,和我们猜测的一样,如果有兴趣的可以看看LinkedHashMap是如何保证顺序的。看一下TreeSetpublicclassTreeSetextendsAbstractSetimplementsNavigableSet,Cloneable,java.io.Serializable{publicTreeSet(){this(newTreeMap());}publicTreeSet(Comparatorcomparator){this(newTreeMap<>(comparator));}publicTreeSet(Collectionc){this();添加所有(c);}publicTreeSet(SortedSets){this(s.comparator());添加全部;}}的确,正如我们所猜测的那样,TreeSet也完全依赖于TreeMap来实现。有兴趣的可以看看TreeMap的实现原理和常用方法。7.总结三章内容,本章结束。虽然Set的实现有点草率,但毕竟他的祖宗是Collection而不是Map。在Map的实现类上穿了一层衣服之后,就变成了Set,然后出于某种目的就在于Collection,哈哈,开个玩笑,本文主要介绍HashSet的原理和主要方法,也简单介绍一下介绍LinkedHashSet和TreeSet,如有不妥之处,请批评指正,希望共同进步,谢谢!近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!