重要参数
- 容量(Capacity) bucket的大小,也就是数组的大小。
- 负载因子(Load factor)bucket填满程度的最大比例。
- modCount,修改或者删除的次数总数。
- threshold,临界值,比 capacity * load factor 大的最小二进制值,一般来表示bucket容量;
- 当bucket中的entries的数目大于 capacity * load factor 时候,需要调整bucket的大小为当前的2倍。
存储结构
put函数的基本实现:
- 对key的hashCode()做二次函数(),然后计算index;
- 如果没碰撞直接放到y方向数组里面(当前index位置上不存在数据);
- 如果发生了碰撞,以链表的形式存放在以数组上的节点为根节点的x方向链表里面;
- 如果碰撞太多导致链表过长(大于默认大小 8 ),就把链表转化为红黑树;
- 如果节点存在的话就替换 valude(保证key的唯一性);
- 如果数组满了(超过了 load factor * current capacity)需要 resize;
|
|
get函数的实现
- 通过 key查找,如果不存在冲突,返回结果;
- 如果有冲突,则通过key.equals(k)去查找对应的entry
若为树,则在树中通过key.equals(k)查找,O(logn);
若为链表,则在链表中通过key.equals(k)查找,O(n)。
|
|
Resize()函数的实现//扩容机制
当put时,如果发现目前的bucket占用程度已经超过了Load Factor所希望的比例,
那么就会发生resize。在resize的过程,简单的说就是把bucket扩充为2倍,之后重
新计算index,把节点再放到新的bucket中。
大致意思就是说,当超过限制的时候会resize,然而又因为我们使用的是2次幂的扩
展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移
动2次幂的位置
eg:当我们从bucket容量从16扩展到32时候(0,1的变化可以认为是随机的)
再重新计算hash之后,因为n变成原来的2倍,n-1在高位多一个1,重新计算后的index
下面看看示意图
备注:
- 在Java 8之前的实现中是用链表解决冲突的,在产生碰撞的情况下,进行get时,两步的时间复杂度是O(1)+O(n)。因此,当碰撞很厉害的时候n很大,O(n)的速度显然是影响速度的。因此在Java 8中,利用红黑树替换链表,这样复杂度就变成了O(1)+O(logn)了,这样在n很大的时候,能够比较理想的解决这个问题;
- (n - 1) & hash,在设计hash函数时,因为目前的table长度n为2的幂,而计算下标的时候,是这样实现的(使用 & 位操作,而非 % 求余)设计者认为这方法很容易发生碰撞。为什么这么说呢?不妨思考一下,在n - 1为15(0x1111)时,其实散列真正生效的只是低4bit的有效位,当然容易碰撞了。因此,设计者想了一个顾全大局的方法(综合考虑了速度、作用、质量),就是把高16bit和低16bit异或了一下。设计者还解释到因为现在大多数的hashCode的分布已经很不错了,就算是发生了碰撞也用 O(logn) 的tree去做了。仅仅异或一下,既减少了系统的开销,也不会造成的因为高位没有参与下标的计算(table长度比较小时),从而引起的碰撞。
- 什么时候会使用HashMap?他有什么特点?是基于Map接口的实现,存储键值对时,它可以接收null的键值,是非同步的,HashMap存储着Entry(hash, key, value, next)对象。
- 你知道HashMap的工作原理吗?通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过 Load Facotr 则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
- 你知道get和put的原理吗?equals()和hashCode()的都有什么作用?通过对key的hashCode()进行hashing,并计算下标( (n-1) & hash ),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点
- 你知道hash的实现吗?为什么要这样实现?在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的: (h =k.hashCode()) ^ (h >>> 16) ,主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。
- 如果HashMap的大小超过了负载因子( load factor )定义的容量,怎么办?如果超过了负载因子(默认0.75),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。
若你觉得我的文章对你有帮助,请添加我为好友
扫描二维码,分享此文章