HashMap 1.7 源码讲解
1、参数讲解
1)hashSeed:用于键的哈希码计算上,用于减少哈希冲突。通过下面所述的initHashSeedAsNeeded方法来进行初始化
2)threshold:表示可以存放的最大量,它的值为loadFactory*数组容量,但是存在最大值为1<<30+1【即2^30+1】
3)loadFactory:负载因子,用于表示元素数量超过数组容量多少时,就会扩容,值在(0,1)之间
4)modCount:每进行一次集合的元素修改,就会进行加1【但是元素的替换并不算集合元素修改之内】
*
2、方法讲解
1)初始化
1、initHashSeedAsNeeded:用于初始化hashSeed
final boolean initHashSeedAsNeeded(int capacity) {
//hashSeed为零 就返回false 不为零 就返回true
boolean currentAltHashing = hashSeed != 0;
//只要useAltHashing为false 那么hashSeed一定为零 sun.misc.VM.isBooted()判断VM是否已经加载完成
//如果没有加载完成,那么就肯定没有hashSeed的值,所以直接设置hashSeed值为零即可
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
2、inflateTable:进行数组的初始化,并且会使用上述方法进行hashSeed的初始化
private void inflateTable(int toSize){
//寻找比当前数大两倍
int capacity = roundUpToPowerOf2(toSize);
//重新设置阈值[是当前容量乘以加载因子]
threshold = (int)Math.min(capacity*loadFactor,MAXMUN_CAPACITY+1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
2)获取元素
3、get方法在调用时会先判断key是否为null,key==null,就会调用getForNullKey方法,否则使用getEntry方法
getForNullKey方法:
private V getForNullKey() {
if (size == 0) {
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
//只要key为null就进行返回
if (e.key == null)
return e.value;
}
return null;
}
getEntry方法:
public Entry<K,V> getEntry(Object key){
if(size==0){
return null;
}
int hash = key==null?0:hash(key);
//找到key存储的对应的entry
Entry<K,V> entries = table[indexFor(hash,table.length)];
for(; entries!=null ; entries = entries.next){
Object k ;
if(entries.hash==hash&&((k=entries.key)==key || (key!=null&&key.equals(k)))){
return entries;
}
}
return null;
}
3)新增元素
3、put:进行元素的添加或者元素替换,key值相同会进行值替换,但是如果进行元素新增,会调用addEntry方法
public V put(K key,V value){
//如果还没有初始化,就进行初始化
if(table==EMPTY_TABLE){
inflateTable(threshold);
}
if(key==null){
return putForNullKey(value);
}
int hash = hash(key);
//判断当前是否具有key是传入的key,如果有就直接进行值替换
for(Entry<K,V> entry=table[indexFor(hash,table.length)] ; entry!=null ; entry = entry.next){
Object k;
if(entry.hash==hash&&((k=entry.key)==key || key.equals(k))){
V oldValue = entry.value;
entry.value = value;
return oldValue;
}
}
//如果不是进行元素替换,肯定会进行修改,所以修改次数加1
modCount++;
//进行元素的添加
addEntry(hash,key,value,indexFor(hash,table.length));
return null;
}
4、putForCreate:也是进行元素的新增,但是方法并不对外开放。添加元素也不会进行modCount的自增。同样添加元素时,会使用createEntry方法进行新增
//不会去检查是否超过阈值,直接计算
private void putForCreate(K key,V value){
int hash = key==null?0:hash(key);
int bucketIndex = indexFor(hash,table.length);
//首先检查是否存在相同的key
for(Entry<K,V> entry = table[bucketIndex] ; entry!=null ; entry = entry.next){
Object k;
if((k = entry.key)==key || (key!=null && key.equals(k))){
entry.value = value;
return;
}
}
//也是增加节点
createEntry(hash,key,value,bucketIndex);
}
5、addEntry方法:进行元素的增加,同时会判断是否需要进行扩容
createEntry方法:也是进行元素的新增,但是并不会进行扩容的判断
//增加元素,并且判断是否超过阈值
void addEntry(int hash,K key,V value,int bucketIndex){
if(size>=threshold && null!=table[bucketIndex]){
resize(2*table.length);
hash = (null!=key)?hash(key):0;
bucketIndex = indexFor(hash,table.length);
}
createEntry(hash,key,value,bucketIndex);
}
//无需判断是否超过阈值,直接添加元素
void createEntry(int hash,K key,V value,int bucketIndex){
Entry<K,V> entry = table[bucketIndex];
//增加
table[bucketIndex] = new Entry<>(hash,key,value,entry);
size++;
}
4)删除元素
6、//删除元素
public V remove(Object key){
Entry<K,V> entry = removeEntryForKey(key);
return entry==null?null:entry.value;
}
//真正进行节点删除的方法
final Entry<K,V> removeEntryForKey(Object key){
if(size==0){
return null;
}
int hash = key==null?0:hash(key);
int bucketIndex = indexFor(hash,table.length);
Entry<K,V> e = table[bucketIndex];
Entry<K,V> pre = e;
while(e!=null){
Entry<K,V> next = e.next;
Object k;
if(next.hash==hash && ((k=e.key)==key || (k!=null&&key.equals(k)))){
modCount++;
size--;
//判断是否第一位就和当前要删除的相等
if(pre==e){
table[bucketIndex] = next;
}else{
pre.next = next;
}
}
pre = e;
e = next;
}
return e;
}
5)扩容
7、resize:进行数组的扩容
transfer:进行数组扩容之后的调整
//进行扩容
void resize(int newCapacity){
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if(oldCapacity == MAXMUN_CAPACITY){
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
//进行数组调整
transfer(newTable,initHashSeedAsNeeded(newCapacity));
table = newTable;
//进行阈值的重新计算
threshold = (int)Math.min(newCapacity*loadFactor,MAXMUN_CAPACITY+1);
}
//进行扩容之后的老数组与新数组的装填
void transfer(Entry[] newTable,boolean rehash){
int newCapacity = newTable.length;
for(Entry<K,V> entry:table){
while(entry!=null){
Entry<K,V> next = entry.next;
用于判断是否进行hash的重新计算
if(rehash){
entry.hash=null==entry.key?0:hash(entry.key);
}
//获取到对应的数组位置
int i = indexFor(entry.hash,newCapacity);
entry.next = newTable[i];
newTable[i] = entry;
entry.next = next;
}
}
}
总结一下:HashMap的数组并不是在初始化的时候就会进行构建的,而是在需要的时候才会进行构建