class HashMap<K,V> extends AbstractMap<K,V>html
1.put()算法
HashMap put()方法源码以下:数组
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //判断当前肯定的索引位置是否存在相同hashcode和相同key的元素,若是存在相同的hashcode和相同的key的元素,那么新值覆盖原来的旧值,并返回旧值。 //若是存在相同的hashcode,那么他们肯定的索引位置就相同,这时判断他们的key是否相同,若是不相同,这时就是产生了hash冲突。 //Hash冲突后,那么HashMap的单个bucket里存储的不是一个 Entry,而是一个 Entry 链。 //系统只能必须按顺序遍历每一个 Entry,直到找到想搜索的 Entry 为止——若是刚好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最先放入该 bucket 中), //那系统必须循环到最后才能找到该元素。 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
hash值冲突是发生在put()时,从源码能够看出,hash值是经过hash(key.hashCode())来获取的,当put的元素愈来愈多时,不免或出现不一样的key产生相同的hash值问题,也便是hash冲突,当拿到一个hash值,经过indexFor(hash, table.length)获取数组下标,先查询是否存在该hash值,若不存在,则直接以Entry<V,V>的方式存放在数组中,若存在,则再对比key是否相同,若hash值和key都相同,则替换value,若hash值相同,key不相同,则造成一个单链表,将hash值相同,key不一样的元素以Entry<V,V>的方式存放在链表中,这样就解决了hash冲突,这种方法叫作分离链表法,与之相似的方法还有一种叫作 开放定址法,开放定址法师采用线性探测(从相同hash值开始,继续寻找下一个可用的槽位)hashMap是数组,长度虽然能够扩大,但用线性探测法去查询槽位查不到时怎么办?所以hashMap采用了分离链表法。性能
2.get()spa
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
有了上面存储时的hash算法做为基础,理解起来这段代码就很容易了。从上面的源代码中能够看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,而后经过key的equals方法在对应位置的链表中找到须要的元素。code
当hashMap没出现hash冲突时,没有造成单向链表,get方法可以直接定位到元素,可是,出现冲突后,造成了单向链表,bucket里存放的再也不是一个entry对象,而是一个entry对象链,系统只能顺序的遍历每一个entry直到找到想要搜索的entry为止,这时,问题就来了,若是刚好要搜索的entry位于该entry链的最末端,那循环必需要进行到最后一步才能找到元素,此时涉及到一个负载因子的概念,hashMap默认的负载因子为0.75,这是考虑到存储空间和查询时间上成本的一个折中值,增大负载因子,能够减小hash表(就是那个entry数组)所占用的内空间,但会增长查询数据的时间开销,而查询是最频繁的操做(put()和get()都用到查询);减少负载因子,会提升查询时间,但会增长hash表所占的内存空间。htm
结合负载因子的定义公式可知,threshold就是在此loadFactor和capacity对应下容许的最大元素数目,超过这个数目就从新resize,以下降实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。当容量超出此最大容量时, resize后的HashMap容量是容量的两倍:对象
3.hashMap数组扩容blog
当HashMap中的元素愈来愈多的时候,hash冲突的概率也就愈来愈高,由于数组的长度是固定的。因此为了提升查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操做也会出如今ArrayList中,这是一个经常使用的操做,而在HashMap数组扩容以后,最消耗性能的点就出现了:原数组中的数据必须从新计算其在新数组中的位置,并放进去,这就是resize。索引
那么HashMap何时进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认状况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,而后从新计算每一个元素在数组中的位置,扩容是须要进行数组复制的,复制数组是很是消耗性能的操做,因此若是咱们已经预知HashMap中元素的个数,那么预设元素的个数可以有效的提升HashMap的性能。
有关负载因子的概念 参考:https://www.cnblogs.com/yesiamhere/p/6653135.html