HashMap解析
了解hashMap前,首先直到Object类中的两个方法。hashCode 和equals.
1 | public native int hashCode(); |
String类中重写了equals方法,比较的是字符串的值。
1 | public boolean equals(Object anObject) { |
当equals被重写时,通常有必要重写hashCode方法,以满足hashCode方法的常规规定,该规定声明相等的对象必须要具有相等的哈希码。
hashCode方法是为哈希家族的集合类框架(HashMap,HashSet,HashTable)提供的服务,hashCode协定:
在同一个对象上多次调用hashCode方法时,必须一致的返回相同的整数。
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
HashMap工作原理说明
工作原理:通过hash算法,通过put和get存储和获取对象。
- 存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Factor则resize为原来的2倍)。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来。如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
- 获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。
HashMap
HashMap是最常用的集合类框架之一,它实现了Map接口,所以存储的元素也是键值对映射的结构,并允许使用null值和null键,其内元素是无序的,如果要保证有序,可以使用LinkedHashMap。HashMap是线程不安全的。
HashMap中我们最长用的就是put(K, V)和get(K)。我们都知道,HashMap的K值是唯一的,那如何保证唯一性呢?我们首先想到的是用equals比较,没错,这样可以实现,但随着内部元素的增多,put和get的效率将越来越低,这里的时间复杂度是O(n),假如有1000个元素,put时需要比较1000次。实际上,HashMap很少会用到equals方法,因为其内通过一个哈希表管理所有元素,也可以称为散列表,哈希算法可以快速的存取元素,当我们调用put存值时,HashMap首先会调用K的hashCode方法,获取哈希码,通过哈希码快速找到某个存放位置,这个位置可以被称之为bucketIndex,通过上面所述hashCode的协定可以知道,如果hashCode不同,equals一定为false,如果hashCode相同,equals不一定为true。所以理论上,hashCode可能存在冲突的情况,有个专业名词叫碰撞,当碰撞发生时,计算出的bucketIndex也是相同的,这时会取到bucketIndex位置已存储的元素,最终通过equals来比较,equals方法就是哈希码碰撞时才会执行的方法,所以前面说HashMap很少会用到equals。HashMap通过hashCode和equals最终判断出K是否已存在,如果已存在,则使用新V值替换旧V值,并返回旧V值,如果不存在 ,则存放新的键值对<K, V>到bucketIndex位置。
HashMap 中put方法的源码
1 | public V put(K key, V value) { |
HashMap的初始长度
首先明确,hashmap的默认初始长度是16,并且每次自动扩展或者手动扩展时,长度必须是2的幂。
之所以选择16,是为了服务于从key映射到index的hash算法。
从key映射到hashMap数组的对应位置,会用到一个hash函数,为了实现一个尽量均匀分布的hash函数,通过key的hashCode值来做运算。
为了实现高效的Hash算法,采用了位运算的方法。
index = HashCode(Key) & (Length-1)
以Book为key演示过程。
- 计算book的hashCode,为10进制的3029737,二进制的1011100011101011101001
- 假定HashMap长度默认16,计算Length-1 为10进制15,二进制1111
- 两个结果做与运算,1011100011101011101001&1111 = 1001 十进制是9,所以index=9.
- 所以Hash算法最终得到的index结果,完全取决于Key的HashCode的最后几位。
这样做不但效果上等同于取模,还大大提高了性能。
要是其他的话,就不平均了,
由于取16,这样index的结果完全等同于HashCode最后几位的值,只要输入的HashCode平均,Hash算法结果就是平均的。
高并发下的HashMap
首先要明白rehash,rehash是HashMap在扩容时候的一个步骤。
HashMap的容量是有限的,当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会越来越高,这时候,就需要扩展长度,resize。
Resize并不是简单的把长度扩大,而是经过下面两个步骤。
- 扩容 创建一个新的Entry空数组,长度是原数组的2倍。
- reHash 遍历原Entry数组,把所有Entry重新Hash到新数组。重新Hash是因为长度扩大后,Hash规则也随之改变。
多线程下HashMap的rehash操作带来的问题
可能会形成链表环。
Author: corn1ng
Link: https://corn1ng.github.io/2017/11/14/HashMap/
License: 知识共享署名-非商业性使用 4.0 国际许可协议