实现系列-HashMap究竟如何存储

首先要结合数据结构的知识,hashmap就是hash表,hash表一定会涉及到冲突的处理,结合源码,详细说一下hashmap的具体实现。
首先在我们用hashmap的时候,一般第一步
HashMap<String,String> map=new HashMap<String,String>();
首先看一下这的步骤完成的事情,
public HashMap() {
     this.entrySet =  null;
     this.loadFactor =  0.75F;
     this.threshold =  12;
     this.table =  new HashMap.Entry[ 16];
     this.init();
}

装载因子 0.75,threshold表示数组现在可容纳最大值,12,初始化了一个16的entry数组,由此可以看到,当数组容量大于12的时候,会进行容量扩充的操作,扩充为二倍。而且,hashmap实际上是以数组形式存储的。
再来看一下put操作。
public V put(K var1, V var2) {
     if(var1 ==  null) {
         return this.putForNullKey(var2);
    }  else {
         int var3 = hash(var1.hashCode());
         int var4 = indexFor(var3,  this.table.length);

         for(HashMap.Entry var5 =  this.table[var4]; var5 !=  null; var5 = var5.next) {
             if(var5.hash == var3) {
                Object var6 = var5.key;
                 if(var5.key == var1 || var1.equals(var6)) {
                    Object var7 = var5.value;
                    var5.value = var2;
                    var5.recordAccess( this);
                     return var7;
                }
            }
        }

        ++ this.modCount;
         this.addEntry(var3, var1, var2, var4);
         return null;
    }
}

第一步判断key是否是null由此可以看到,hashmap是可以插入null的。
如果不为null,那么进行第一步,计算key的hash值,也就是var3,然后根据hash值,算出来这个hash值对应的key在数组中的位置,这里就涉及到一个问题,一样怎么半,这个时候,就是hash冲突的处理了。

如果相等了,说明table[var4]肯定不为null,那么var5不为null,var5的next是否为null不知道,然后循环判断,如果有一个已经是hash值相同,并且key值也相同,那么就可以覆盖这个key的value值了,如果hash值相同,但是不存在put的这个key,那么return var7这个就不会执行。

会执行addEntry这个函数,记住传入的参数,var3 hash值,var 1,var 2要插入元素的key value  ,var4 hash值在数组中对应的位置。
void addEntry( int var1, K var2, V var3,  int var4) {
    HashMap.Entry var5 =  this.table[var4];
     this.table[var4] =  new HashMap.Entry(var1, var2, var3, var5);
     if( this.size++ >=  this.threshold) {
         this.resize( this.table.length);
    }

}

首先取出table数组中,var4位置的元素,然后用var1,var2这个键值对新建一个entry,这个entry后面类似于链表一样,连接着var5。这个var5就是以前var4位置的元素。然后用新建的元素充当新的var4,由此可以推断,对于冲突的处理,采用链表法,而且新加入的元素在链表头部。
static class Entry<K, V>  implements java.util.Map.Entry<K, V> {
     final K key;
    V value;
    HashMap.Entry<K, V> next;
     final int hash;

    Entry( int var1, K var2, V var3, HashMap.Entry<K, V> var4) {
         this.value = var3;
         this.next = var4;
         this.key = var2;
         this.hash = var1;
    }


这个是entry的定义,可以看出是采用链表链接表示
if( this.size++ >=  this.threshold) {
         this.resize( this.table.length);
    }
这个代码就是之前说的扩容。
问题1:什么时候调用addEntry这个函数
答:当数组table中,var4位置为null的时候和向var4这个位置链接key值不同,但是hash值相同的元素时就会调用,否则不调用

问题2:当容量扩充的时候,get时如何获取到正确位置,因为数组容量已经发生了改变了。

答:
void resize( int var1) {
    HashMap.Entry[] var2 =  this.table;
     int var3 = var2.length;
     if(var3 ==  1073741824) {
         this.threshold =  2147483647;
    }  else {
        HashMap.Entry[] var4 =  new HashMap.Entry[var1];
         this.transfer(var4);
         this.table = var4;
         this.threshold = ( int)(( float)var1 *  this.loadFactor);
    }
}

这个是resize函数,首先var3是当前数组容量,var1是扩充以后数组容量,重点是transfer这个函数,
void transfer(HashMap.Entry[] var1) {
    HashMap.Entry[] var2 =  this.table;
     int var3 = var1.length;

     for( int var4 =  0; var4 < var2.length; ++var4) {
        HashMap.Entry var5 = var2[var4];
         if(var5 !=  null) {
            var2[var4] =  null;

            HashMap.Entry var6;
             do {
                var6 = var5.next;
                 int var7 = indexFor(var5.hash, var3);
                var5.next = var1[var7];
                var1[var7] = var5;
                var5 = var6;
            }  while(var6 !=  null);
        }
    }

}

transfer这个函数中,var1是扩充的数组,var2是当前数组,一个for循环依次遍历,如果var4位置不为null的话,将var4位置元素给var5,然后var4位置元素设置为null,然后遍历var5以后的元素,具体效果为
如果var4位置的链表形式为1-》2-》3,那么这次以后就会变为3-》2-》1,也就是会反过来,为什么会这样,indexFor(var5.hash, var3) 在确定具体数组位置的时候,var3是扩充以后数组容量,所以会导致重新定位,这个就是为什么要将
            var2[var4] =  null;设置为null,以及扩容后也能正确寻找到定位的原因了。



问题3:扩容的时候,条件是
void addEntry( int var1, K var2, V var3,  int var4) {
    HashMap.Entry var5 =  this.table[var4];
     this.table[var4] =  new HashMap.Entry(var1, var2, var3, var5);
     if( this.size++ >=  this.threshold) {
         this.resize( this.table.length);
    }

}

     if( this.size++ >=  this.threshold)  这句话成立会扩容,之前讲过,有两个地方会调用这个函数,table数组中var4位置为null,或者var4不为null,但是添加元素的key值与var4中已经链接的元素indexfor值一样的时候一样,会发生value值的替换。也就是说可能存在这样一种情况,对于容量为16的数组,在数组的某个位置上,可能存在1->2->3->4…->12这种情况,这种情况下,再添加元素的时候
 就会引起扩容操作,但是此时数组上只有一个位置有元素,而且链表很长。

这样设计的原因为啥啊?,暂时当作疑问吧,如果限制为size仅仅是var4位置为null,第一次填充的时候,会不会更好一些呢

问题4:如果在var4位置的链表上,1-》2-》3,
1,2,3 三满足什么样的条件?

答:indexFor的值相同,但是hash值可能不同,因为indexFor的函数定义
static int indexFor( int var0,  int var1) {
     return var0 & var1 -  1;
}
var0是hash值,var1是数组长度,
可能存在hash值不同,但是indexFor的值相同的情况














  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值