hashmap的初始容量和装载因子Word格式.docx
《hashmap的初始容量和装载因子Word格式.docx》由会员分享,可在线阅读,更多相关《hashmap的初始容量和装载因子Word格式.docx(4页珍藏版)》请在冰豆网上搜索。
_____________________________________________
//HashMap的构造
publicHashMap(intinitialCapacity,floatloadFactor){
if(initialCapacity<
0)
thrownewIllegalArgumentException("
Illegalinitialcapacity:
"
+
initialCapacity);
if(initialCapacity>
MAXIMUM_CAPACITY)
initialCapacity=MAXIMUM_CAPACITY;
if(loadFactor<
=0||Float.isNaN(loadFactor))
Illegalloadfactor:
loadFactor);
//Findapowerof2>
=initialCapacity
intcapacity=1;
while(capacity<
initialCapacity)
capacity<
<
=1;
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(Entry<
K,V>
e=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){
Entry<
e=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;
next;
看到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
intcapacity=1;
threshold=(int)(capacity*loadFactor);
}重点注意这里
while(capacity<
capacity才是初始容量,而不是initialCapacity,这个要特别注意,如果执行newHashMap(9,0.75);
那么HashMap的初始容量是16,而不是9,想想为什么吧。
构造函数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];
}构造函数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);
10);
returnh;
privatestaticintnewHash(inth){
//ThisfunctionensuresthathashCodesthatdifferonlyby
//constantmultiplesateachbitpositionhaveabounded
//numberofcollisions(approximately8atdefaultloadfactor).
h^=(h>
20)^(h>
12);
returnh^(h>
7)^(h>
}其实HashMap的哈希函数会一直都是oldHash。
如果确定数据的位置看下面两行
inthash=hash(k.hashCode());
第一行,上面讲过了,是得到哈希值,第二行,则是根据哈希指计算元素在数组中的位置了,位置的计算是将哈希值和数组长度按位与运算。
staticintindexFor(inth,intlength){
returnh&
(length-1);
}put方法到底作了什么?
publicVput(Kkey,Vvalue){
addEntry(hash,key,value,i);
}如果key为NULL,则是单独处理的,看看putForNullKey方法:
privateVputForNullKey(Vvalue){
inthash=hash(NULL_KEY.hashCode());
if(e.key==NULL_KEY){
addEntry(hash,(K)NULL_KEY,value,i);
}NULL_KEY的声明:
staticfinalObjectNULL_KEY=newObject();
这一段代码是处理哈希冲突的,就是说,在数组某个位置的对象可能并不是唯一的,它是一个链表结构,根据哈希值找到链表后,还要对链表遍历,找出key相等的对象,替换它,并且返回旧的值。
}如果遍历完了该位置的链表都没有找到有key相等的,那么将当前对象增加到链表里面去
且看看addEntry方法
voidaddEntry(inthash,Kkey,Vvalue,intbucketIndex){
Entry<
table[bucketIndex]=newEntry<
resize(2*table.length);
}table[bucketIndex]=newEntry<
新建一个Entry对象,并放在当前位置的Entry链表的头部,看看下面的Entry构造函数就知道了,注意红色部分。
Entry(inth,Kk,Vv,Entry<
n){
value=v;
next=n;
key=k;
hash=h;
}如何扩容?
当put一个元素时,如果达到了容量限制,HashMap就会扩容,新的容量永远是原来的2倍。
上面的put方法里有这样的一段:
if(size++>
这是扩容判断,要注意,并不是数据尺寸达到HashMap的最大容量时才扩容,而是达到threshold指定的值时就开始扩容,threshold=最大容量*加载因子。
看看resize方法
voidresize(intnewCapacity){
transfer(newTable);
}重点看看红色部分的transfer方法
voidtransfer(Entry[]newTable){
Entry[]src=table;
intnewCapacity=newTable.length;
for(intj=0;
j<
src.length;
j++){
e=src[j];
if(e!
=null){
src[j]=null;
do{
next=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){
returngetForNullKey();
e=table[indexFor(hash,table.length)];