对HashMap迭代的理解

对程序中有如下代码
指定HashMap的键值对key是字符串,value是整数类型
 HashMap<String ,Integer> maps=new HashMap<String,Integer>();
	     maps.put("name",520);
	     maps.put("age",24);
	     maps.put("nima",55);
	     
	     Set<Map.Entry<String,Integer>> entrySet=maps.entrySet();
//用增强for循环方式迭代取出键值对的具体值
         for(Map.Entry<String,Integer> entry:entrySet){
        	 System.out.println(entry.getKey()+":"+entry.getValue());
         }
         

输出的结果如下

age:24
name:520
nima:55

通过对JDK文档的了解HashMap实现了Map的接口

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

Map集合有一个方法,返回值是 Set<Map.Entry<K, V>>

Set<Map.Entry<K, V>> entrySet();
HashMap实现了这个方法,返回值是 Set<Map.Entry<K, V>>

public Set<Map.Entry<K,V>> entrySet() {
	return entrySet0();
    }

    private Set<Map.Entry<K,V>> entrySet0() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }
Map.Entry<K,V>是一个接口,里面有方法


接下来说明一下对HashMap 的理解

    要理解HashMap是什么,首先要明白它的数据结构,在java编程中,最基本的结构就是两种,一个是数组,另一个是模拟指针(也就是引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap一样。HashMap是一个数组和链表的结合体,这在数据结构中一般称为链表散列,如下图所示


从图中可以分析道HashMap就是一个数组结构,当新建一个HashMap的时候,就会初始化一个数组,接下来看看java代码

static class Entry<K,V> inplements Map.Entry<K,V>{

    final K key;

    V value;

   final int hash;

   Entry<K,V> next;

 ......................................

}

上面的entry就是数组中的元素,它持有一个指向下一个元素的引用,就构成了链表。

当我们往HashMap中put元素的时候,先根据key和value的值得到这个元素在数组中的位置(下标),然后就额可以把这个元素放在对用的位置。如果这个元素所在的位置已经存放其它元素了,那么在通一个位置上的元素将以链表的形式存放,新加入的放在链头。从HashMap中get元素的时候,首先计算key的hashcode。找到数组中对应的位置的某一个元素,然后通过key的equals方法在对应的位置链表找到需要的元素,在这里可以做一个假设,如果每个位置上的链表只有一个元素,那么HashMap的get效率是最高的。

     Hash算法

    我们可以看到在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置,如何计算这个位置就是hash算法。前面说过HashMap的数据结构式数组和链表的结合,所以我们也非常希望这个HashMap里面的元素分布是均匀的,尽量是的每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表的全部元素。

    所以我们一开始就可能想到吧HashMap对数组的长度进行取模运算,这样一来元素的分布相对来说是比较均匀的。但是“模”运算的消耗还是比较大的。那么有什么方法是更加好的呢?

    static int indexFor(int h,int length){

     return h&(length-1);

}

首先算得key的hashcode值。然后跟数组的长度-1做一次与运算。看上去还是很简单的。但是里面还是有玄机的。例如数组的长度是2的4次方,那么hashcode就会和2的4次方-1做与运算。那么为什么HashMap的数组初始化大小都是2的某次方大小时的效率是最高的呢。我们以2的4次方作为一个例子,来解释一个为什么数组大小为2的幂时HashMap访问的性能最高的。

        看如下图所示,左边两组是数组长度为16也就是2的4次方,右边两组是数组长度为15.两组的hashcode均为8和9,但是很明显,当他们和1110与的时候,产生相同的结果,也就是说他们会定位到数组中的同一个位置上,这就产生了碰撞,8和9被放在同一个链表上,那么查询的时候就需要遍历这个链表得到8或者9,这样就降低了查询效率。同事我们也可以发现,当数组长度为15的时候hashcode的值会与14(1110)进行与操作,那么最后一位永远是0,二0001,0011,0101,1001,1011,0111,1101这几个位置永远都不会存放元素了,空间浪费相当大,更糟糕的是在这种情况下,数组可以使用的位置比数组长度小很多,这样就说明了进一步增加了碰撞的几率,减慢了查询的效率


所以说当数组长度为2的N次幂的时候,不同的key算的得到index相同的几率比较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率就比较小,相对而言,查询的时候就不用遍历某个位置上的链表,这样查询效率就会高很多。

 那么HashMap中默认的数组大小是多少呢,根据查询可以得知是16,为什么是16呢?而不是15或者20呢,那就是上面的说明可以解释了吧。显然是因为16是2的整数次幂的原因。在小数据量的时候16比起15或者20更能减少key之间的碰撞,从而加快查询效率。









  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值