对程序中有如下代码
指定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之间的碰撞,从而加快查询效率。