hashmap的初始容量和装载因子.docx
《hashmap的初始容量和装载因子.docx》由会员分享,可在线阅读,更多相关《hashmap的初始容量和装载因子.docx(4页珍藏版)》请在冰豆网上搜索。
hashmap的初始容量和装载因子
HashMap的初始容量(initialCapacity)和装载因子(loadFactor)
按HashMap源码里的那种重构方法,如果reHash过多,显然会影响性能。
所以为了防止过多的reHash,我们需要自己配置HashMap的装载因子loadFactor和初始的table容量capacity的大小(可以在构造函数里配或者调用方法配)。
很容易理解,如果我们已经知道我们使用的HashMap一般情况的存储在1W对以上,你给它一个默认的16的初始的table容量,默认reHash每次容量翻倍,这得重构多少次呀!
(如果装载因子为1,还得要约5~6次)。
但是如果我们的对HashMap的容量需求不是很大,你给它一个默认1W的容量,显然又浪费宝贵的空间了。
至于这两个参数的选择可以自己去把握,甚至可以设定动态绑定:
分析历史数据,找出规律,或者预测未来的走向找出规律。
对HashMap这两个参数实现一个动态的调整。
比如早上8点~9点A业务比较忙,它对应的HashMap可以提前多给些空间,而10点以后B业务使用的HashMap比较忙,A相对清闲,可以缩减A的空间给B。
如果从数据库的表中读取记录存入HashMap中,完全可以根据记录的行数(rowsize)来初始化HashMap的容量,这样就可以达到reHash的最少次数,同时也保证了HashMap所需的最小容量:
比如:
通过SQL语句:
selectcount(字段)asrowSizefrom表
提到行数:
rowSize=30
那么我们在定义HashMap的时候:
HashMaph=newHashMap(rowSize,1f);
下面是HashMap的相关源代码部分:
_____________________________________________
//HashMap的构造
publicHashMap(intinitialCapacity,floatloadFactor){ if(initialCapacity<0) thrownewIllegalArgumentException("Illegalinitialcapacity:
"+ initialCapacity); if(initialCapacity>MAXIMUM_CAPACITY) initialCapacity=MAXIMUM_CAPACITY; if(loadFactor<=0||Float.isNaN(loadFactor)) thrownewIllegalArgumentException("Illegalloadfactor:
"+ loadFactor);
//Findapowerof2>=initialCapacity intcapacity=1; while(capacity this.loadFactor=loadFactor; threshold=(int)(capacity*loadFactor); table=newEntry[capacity]; init(); }
publicV put(Kkey,Vvalue){ if(key==null) returnputForNullKey(value); inthash=hash(key.hashCode()); inti=indexFor(hash,table.length); for(Entrye=table[i];e!
=null;e=e.next){ Objectk; if(e.hash==hash&&((k=e.key)==key||key.equals(k))){ VoldValue=e.value; e.value=value; e.recordAccess(this); returnoldValue; } }
modCount++; addEntry(hash,key,value,i); returnnull; }
//添加新的项目voidaddEntry(inthash,Kkey,Vvalue,intbucketIndex){ Entrye=table[bucketIndex]; table[bucketIndex]=newEntry(hash,key,value,e); if(size++>=threshold) //注意理解这里,当前的实际大小(size)与threshold相等时,就将当前的容量扩大一倍 { resize(2*table.length); } }
//重构大小voidresize(intnewCapacity){ Entry[]oldTable=table; intoldCapacity=oldTable.length; if(oldCapacity==MAXIMUM_CAPACITY){ threshold=Integer.MAX_VALUE; return; }
Entry[]newTable=newEntry[newCapacity]; transfer(newTable); table=newTable; threshold=(int)(newCapacity*loadFactor); }
HashMap的数据结构 HashMap主要是用数组来存储数据的,我们都知道它会对key进行哈希运算,哈系运算会有重复的哈希值,对于哈希值的冲突,HashMap采用链表来解决的。
在HashMap里有这样的一句属性声明:
transientEntry[]table;Entry就是HashMap存储数据所用的类,它拥有的属性如下finalKkey;Vvalue;finalinthash;Entrynext;
看到next了吗?
next就是为了哈希冲突而存在的。
比如通过哈希运算,一个新元素应该在数组的第10个位置,但是第10个位置已经有Entry,那么好吧,将新加的元素也放到第10个位置,将第10个位置的原有Entry赋值给当前新加的Entry的next属性。
数组存储的是链表,链表是为了解决哈希冲突的,这一点要注意。
几个关键的属性存储数据的数组transientEntry[]table; 这个上面已经讲到了默认容量staticfinalintDEFAULT_INITIAL_CAPACITY=16;最大容量staticfinalintMAXIMUM_CAPACITY=1<<30;默认加载因子,加载因子是一个比例,当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容staticfinalfloatDEFAULT_LOAD_FACTOR=0.75f;当实际数据大小超过threshold时,HashMap会将容量扩容,threshold=容量*加载因子intthreshold;加载因子finalfloatloadFactor;HashMap的初始过程构造函数1 publicHashMap(intinitialCapacity,floatloadFactor){ if(initialCapacity<0) thrownewIllegalArgumentException("Illegalinitialcapacity:
"+ initialCapacity); if(initialCapacity>MAXIMUM_CAPACITY) initialCapacity=MAXIMUM_CAPACITY; if(loadFactor<=0||Float.isNaN(loadFactor)) thrownewIllegalArgumentException("Illegalloadfactor:
"+ loadFactor); //Findapowerof2>=initialCapacity intcapacity=1; while(capacity构造函数2publicHashMap(intinitialCapacity){ this(initialCapacity,DEFAULT_LOAD_FACTOR); }构造函数3,全部都是默认值 publicHashMap(){ this.loadFactor=DEFAULT_LOAD_FACTOR; threshold=(int)(DEFAULT_INITIAL_CAPACITY*DEFAULT_LOAD_FACTOR); table=newEntry[DEFAULT_INITIAL_CAPACITY]; init(); }构造函数4 publicHashMap(Map
extendsK,?
extendsV>m){ this(Math.max((int)(m.size()/DEFAULT_LOAD_FACTOR)+1, DEFAULT_INITIAL_CAPACITY),DEFAULT_LOAD_FACTOR); putAllForCreate(m); }如何哈希 HashMap并不是直接将对象的hashcode作为哈希值的,而是要把key的hashcode作一些运算以得到最终的哈希值,并且得到的哈希值也不是在数组中的位置哦,无论是get还是put还是别的方法,计算哈希值都是这一句:
inthash=hash(key.hashCode());hash函数如下:
staticinthash(inth){ returnuseNewHash?
newHash(h):
oldHash(h); }useNewHash声明如下:
privatestaticfinalbooleanuseNewHash; static{useNewHash=false;}这说明useNewHash其实一直为false且不可改变的,hash函数里对useNewHash的判断真是多余的。
privatestaticintoldHash(inth){ h+=~(h<<9); h^= (h>>>14); h+= (h<<4); h^= (h>>>10); returnh; } privatestaticintnewHash(inth){ //ThisfunctionensuresthathashCodesthatdifferonlyby //constantmultiplesateachbitpositionhaveabounded //numberofcollisions(approximately8atdefaultloadfactor). h^=(h>>>20)^(h>>>12); returnh^(h>>>7)^(h>>>4); }其实HashMap的哈希函数会一直都是oldHash。
如果确定数据的位置看下面两行 inthash=hash(k.hashCode()); inti=indexFor(hash,table.length);第一行,上面讲过了,是得到哈希值,第二行,则是根据哈希指计算元素在数组中的位置了,位置的计算是将哈希值和数组长度按位与运算。
staticintindexFor(inth,intlength){ returnh&(length-1); }put方法到底作了什么?
publicVput(Kkey,Vvalue){ if(key==null) returnputForNullKey(value); inthash=hash(key.hashCode()); inti=indexFor(hash,table.length); for(Entrye=table[i];e!
=null;e=e.next){ Objectk; if(e.hash==hash&&((k=e.key)==key||key.equals(k))){ VoldValue=e.value; e.value=value; e.recordAccess(this); returnoldValue; } } modCount++; addEntry(hash,key,value,i); returnnull; }如果key为NULL,则是单独处理的,看看putForNullKey方法:
privateVputForNullKey(Vvalue){ inthash=hash(NULL_KEY.hashCode()); inti=indexFor(hash,table.length); for(Entrye=table[i];e!
=null;e=e.next){ if(e.key==NULL_KEY){ VoldValue=e.value; e.value=value; e.recordAccess(this); returnoldValue; } } modCount++; addEntry(hash,(K)NULL_KEY,value,i); returnnull; }NULL_KEY的声明:
staticfinalObjectNULL_KEY=newObject();这一段代码是处理哈希冲突的,就是说,在数组某个位置的对象可能并不是唯一的,它是一个链表结构,根据哈希值找到链表后,还要对链表遍历,找出key相等的对象,替换它,并且返回旧的值。
for(Entrye=table[i];e!
=null;e=e.next){ if(e.key==NULL_KEY){ VoldValue=e.value; e.value=value; e.recordAccess(this); returnoldValue; } }如果遍历完了该位置的链表都没有找到有key相等的,那么将当前对象增加到链表里面去 modCount++; addEntry(hash,(K)NULL_KEY,value,i); returnnull;且看看addEntry方法 voidaddEntry(inthash,Kkey,Vvalue,intbucketIndex){ Entrye=table[bucketIndex]; table[bucketIndex]=newEntry(hash,key,value,e); if(size++>=threshold) resize(2*table.length); }table[bucketIndex]=newEntry(hash,key,value,e);新建一个Entry对象,并放在当前位置的Entry链表的头部,看看下面的Entry构造函数就知道了,注意红色部分。
Entry(inth,Kk,Vv,Entryn){ value=v; next=n; key=k; hash=h; }如何扩容?
当put一个元素时,如果达到了容量限制,HashMap就会扩容,新的容量永远是原来的2倍。
上面的put方法里有这样的一段:
if(size++>=threshold) resize(2*table.length);这是扩容判断,要注意,并不是数据尺寸达到HashMap的最大容量时才扩容,而是达到threshold指定的值时就开始扩容,threshold=最大容量*加载因子。
看看resize方法 voidresize(intnewCapacity){ Entry[]oldTable=table; intoldCapacity=oldTable.length; if(oldCapacity==MAXIMUM_CAPACITY){ threshold=Integer.MAX_VALUE; return; } Entry[]newTable=newEntry[newCapacity]; transfer(newTable); table=newTable; threshold=(int)(newCapacity*loadFactor); }重点看看红色部分的transfer方法 voidtransfer(Entry[]newTable){ Entry[]src=table; intnewCapacity=newTable.length; for(intj=0;je=src[j]; if(e!
=null){ src[j]=null; do{ Entrynext=e.next; inti=indexFor(e.hash,newCapacity); e.next=newTable[i]; newTable[i]=e; e=next; }while(e!
=null); } } }tranfer方法将所有的元素重新哈希,因为新的容量变大,所以每个元素的哈希值和位置都是不一样的。
正确的使用HashMap1:
不要在并发场景中使用HashMap HashMap是线程不安全的,如果被多个线程共享的操作,将会引发不可预知的问题,据sun的说法,在扩容时,会引起链表的闭环,在get元素时,就会无限循环,后果是cpu100%。
看看get方法的红色部分publicVget(Objectkey){ if(key==null) returngetForNullKey(); inthash=hash(key.hashCode()); for(Entrye=table[indexFor(hash,table.length)];