JDK1.7 HashMap源码解析

     在jdk1.7中,HashMap底层数据结构是由数组和链表构成,数组存放Entry对象,而Entry对象是对链表头部第一个元素的引用。ArrayList中add方法是通过下标的累加进行数据的插入,HashMap则不同。

     HashMap通过获取key的HashCode值,而HashMap中构成其数组的默认长度为16,将HashCode的值通过公式“hashcode(key)&(length-1)”,获取下标的值,这个下标的值在不扩容的情况下永远都在0-15之间,就是HashMap数组的长度。在确定下标后,会以头插法的方法插入的链表中(尾插法需要一个一个遍历找到最后一个null数据,相对头插法会损失更多的时间),然后Entry对象重新指向链表新的元素。若put的key相同,在链表中会先进行循环遍历是否存在相同的key,存在会覆盖旧的链表元素,然后put结束后会把旧的元素返回。通过采用这种“链址法”也解决了Hash碰撞的问题。

   如果指定了HashMap数组的长度  HashMap hashMap = new Map(10);HashMap中会通过“roundUpToPowerOf2(10)“方法设置HashMap中数组容量为>=10的幂次方,及二的四次方16,从而确定数组容量。具体看源码。如果长度不是16,length-1的二进制就全是1组成的。 10010,这样的话有些index会永远算出来,增加了链表的长度,降低了空间的利用率,造成get(key)方法效率低下。为了让HashCode算出来的下标散列开来,源码中对hashCode的值进行了移位运算,目的就是提高数组的散列程度,提高查询的效率。

  HashMap的扩容问题。默认的数组长度为16当插入的数据大于阈值12(16*0.75负载因子),数组将会以两倍扩容,扩容会创建新的两倍数组的长度,将旧的链表循坏遍历重新指向新的数组中,在重新指向时,重新组成的链表会更加散列,并且链表的顺序在扩容后会完全相反,然后将创建的新数组重新赋值给旧的数组,完成HashMap的扩容。然而,当多个线程同时执行HashMap的扩容时,因为是头插法有可能会导致链表的死循环,导致CPU被吃满,这就是HashMap是线程不安全的原因。

  HashMap扩容会带来rehash操作,根据不同虚拟机参数"jdk.map.althashing.threshold"的设置,如果容量大于等于这个值,就会造成rehash,rehash会让数组的链表散列性更好,提高HashMap的put方法查询效率,但是rehash也会消耗内存。这个可以根据需要来修改虚拟机的值。

 modCount++,在HashMap源码中会造成异常(CurrentModificationException)。无论是put还是add都会使modCount++。

    Iterator i = hashMap.keySet().iterator();
    while(i.hasNext()){
    Sting key = (String)i.next();
    if(key.equals("key")){
        hashMap.remove();  //modCount++造成异常,  需要替换成i.remove()方法,会同步modCount;
    }
}

这是一种fastFail(快速失败)方式,提高容错率,因为HashMap本身就不是线程安全的,不管删除还是添加,都会对modCount造成累加,在多线程当中多个线程同时操作(查询、删除)同一个HashMap的时候,就会抛出CurrentModificationException,结束下面的代码执行。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值