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

Set集合底层分析

时间:2023-04-02 01:56:48 Java

HashSet特性允许元素为null不可重复hashmap(数组+链表+红黑树)不安全无序扩容机制初始化一个长度为16的数组,并根据16x0.75的负载因子,你会得到一个临界值12,也就是说这个临界值就是后面真正触发扩容的值,我不等你满了再扩容,而且是当收集元素达到-当前容量*0.75=临界值时,触发扩容,每次扩容翻倍,计算临界值。临界值=电流容量*0.75。这个临界值不是指hashmap数组的大小,而是指collection的大小。hashmap中有一个表数组,这个数组中的每个下标(索引/元素和)都可能有链节点,如果每个链节点元素/数组的长度大于12,就会触发扩容。当集合中的元素没有达到临界值时,如果这个数组中某个节点的链节点元素>=8,就会触发转换成红黑树,前提是如果这个数组的长度达到64个长度,如果长度小于64,会重新展开。扩容机制是当前数组长度的两倍,计算临界值。当临界值被触发时,扩张机制也会被触发。如果这个链结点>=8,数组长度达到64,这个结点就会转化为红黑树解析1.初始化HashSet1.1从源码看,hashset的初始化实际上是初始化了一个hashMap。/***构造一个新的空集;后备HashMap实例具有*默认初始容量(16)和负载因子(0.75)。*/publicHashSet(){map=newHashMap<>();}2。集合初始化后,执行第一个添加操作。publicbooleanadd(Ee){//此时会调用hashmap的put进行put操作//而PRESENT是全局共享的最终常量,所以每次添加hashset时,put操作都是执行,key为新添加元素的value,value始终为PRESENT//privatestaticfinalObjectPRESENT=newObject();返回map.put(e,PRESENT)==null;}2.1根据元素的哈希值得到数组的索引staticfinalinthash(Objectkey){inth;返回(键==空)?0:(h=key.hashCode())^(h>>>16);}2.2判断元素是否已经存在用于扩容,是红黑树结构还是链表。finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){//定义辅助变量Node[]tab;节点p;诠释n,我;//1.如果当前表的引用为空或者长度为空,则进入扩容方法//2.扩容会按照当前默认长度16进行扩容,临界值(12)为根据负载因子得到(0.75)//3.此时哈希表的长度为16if((tab=table)==null||(n=tab.length)==0)n=(tab=resize()).length;//1.根据加法元素计算的哈希值得到数组的索引(`&`是按位运算符,就是得到两个值的二进制数并进行`&`oneachnumber。二进制数只有1或0。比较时,1&1=1,1&0=0,0&0=0)//2.如果计算出的索引值在表中为空,直接加上hash值,key,并将元素的值赋给表的相应索引。如果((p=tab[i=(n-1)&hash])==null)tab[i]=newNode(hash,key,value,null);else{节点e;;//如果这个表中索引对应的元素已经存在//1.比较新元素的哈希值是否等于表中索引的哈希值//2.以及两个对象的键元素是否相等相等或者新元素key不为空,并且//取新元素的值(k)和表中的值(k)进行equals比较,这里equals方法//如何使用完全由我们控制,如果equals被重写,比较对象的值。如果不重写equals,则为比较对象的地址值if(p.hash==hash&&((k=p.key)==key||(key!=null&&key.equals(k))))//如果两个元素相等,则分配数组中索引的对象。e=p;//如果这个表中已经存在的元素是红黑树,那么就使用红黑树来添加元素。elseif(pinstanceofTreeNode)e=((TreeNode)p).putTreeVal(this,tab,hash,key,value);//如果是链表else{for(intbinCount=0;;++binCount){//如果是链表,循环读取,如果链表的节点的下一个元素index为空if((e=p.next)==null){//如果下一个节点为空,则直接元素的下一个节点为空,直接创建新节点,并进行hash,传入元素的键和值。p.next=newNode(哈希,键,值,空);//如果当前链表长度>=8if(binCount>=TREEIFY_THRESHOLD-1)//-1for1st//如果链表节点长度>=8//1.先回去判断当前表的长度是否为64,如果不是64//则对表进行扩容(当前容量+当前容量),计算临界值//2.如果这个链表的长度>=8且表table的长度已经等于64//然后将表table中这个索引的链表转化为红黑树treeifyBin(tab,hash);休息;}//如果在查询这个链表的过程中,如果发现链节点的元素key等于新元素的key,则不添加,直接退出//这里比较的是table表中索引的下一个元素,因为索引的第一个元素在上面已经比较过了。如果(e.hash==hash&&((k=e.key)==key||(key!=null&&key.equals(k))))break;p=e;}}//ifnot如果为空,说明添加元素失败。if(e!=null){//keyVoldValue=e.value的现有映射;如果(!onlyIfAbsent||oldValue==null)e.value=value;节点访问后(e);返回旧值;}}++模数;//比较添加元素后的次数和`criticalvalue`,如果大于`criticalvalue`,再次展开if(++size>threshold)resize();节点插入后(逐出);返回空值;}LinkedHashSet特点有序linkedHashMap(数组+双向链表)不可重复,可以为空。不安全的扩容机制因为linkedHashSet继承自HashSet,固扩机制等同于HashSet和HashMap分析解释1.LinkedHashSet中维护了一个哈希表和一个双向链表,包含head和tail2。表中的每个节点都有before和after属性,可以组成一个双向链表。3、添加元素时,先求哈希值,再求索引,确定元素表的索引位置,然后将元素添加到双向链表中(如果已经存在则不能添加,即相当于hashset)4.因为表中索引的每个节点都包含前后属性,索引的每个节点之间是有序链接的,即形成一个有序的双向链表结构图