HashMap是一个利用哈希表原理来存储元素的集合。遇到冲突时,HashMao是采用的链地址法来解决,在JDK1.8中,HashMap是由 数组+链表+红黑树构成,新增了红黑树作为底层数据结构,结构变复杂了,但是效率也变的更高了
1、HashMap定义
该类实现了Map接口、Cloneable接口、Serializable接口,继承了AbstractMap抽象类,在Map接口中定义了一组键值对映射通用的操作。
2、字段属性
后面三个字段是JDK1.8新增的,主要是用来进行红黑树和链表的互相转换。
- Node<K,V>[] table
上面说过HashMap是由数组+链表+红黑树组成,这里的数组就是table字段,后面对其进行初始化长度默认是DEFAULT_INITIAL_CAPACITY= 16
- Size
集合中存放key-value的实时对数
- loadFactor
装载因子,用来衡量HashMap满的程度,计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。Capacity是桶的数量,也就是table的长度
- Threshold
计算公式:capacity * loadFactor。这个值是当前已占用数组长度的最大值。过这个数目就重新resize(扩容),扩容后的 HashMap 容量是之前容量的两倍
3、构造函数
①、默认无参构造函数
②、指定初始容量的构造函数
这段代码的目的是把n的二进制数中的0置为1,比如2是10经过处理后变成11。
4、确定哈希桶数组索引位置
HashMap是数组+链表+红黑树的组合,我们希望在有限个数组位置时,尽量每个位置的元素只有一个,那么当我们用散列函数求得索引位置的时候,我们能马上知道对应位置的元素是不是我们想要的,而不是进行链表的遍历或者红黑树的遍历,HashMap中的哈希算法:
这里获取hashCode()方法的值是变量,因为bucket数组大小是2的幂,计算下标index = (table.length - 1) & hash,如果不做hash处理,相当于散列生效的只有几个低 bit 位。hash方法实际是让key.hashCode()与key.hashCode()>>>16进行异或操作,高16位不变,低16位和高16位做了一个异或处理,目的是减少碰撞。
5、添加元素
①、判断键值对数组 table 是否为空或为null,否则执行resize()进行扩容;
②、根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③、判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④、判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤、遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥、插入成功后,判断实际存在的键值对数量size是否超过了最大容量threshold,如果超过,进行扩容。
⑦、如果新插入的key不存在,则返回null,如果新插入的key存在,则返回原key对应的value值(注意新插入的value会覆盖原value值)
6、扩容机制
当集合是由数组+链表+红黑树构成,向HashMap中插入元素时,如果HashMap集合的元素已经大于了最大承载容量threshold(capacity*loadFactor),这里的threshold不是数组的最大长度,但Java中数组无法自动扩容的,所以采用的方法是用一个更大的数组代替这个小的数组。
该方法分为两个部分,首先是计算新桶数组的容量newCap和新阙值newThr,然后将原集合的元素重新映射到新集合中。
前半部分:计算新桶数组的容量newCap和新阙值newThr
后半部分:将原集合的元素重新映射到新集合中。JDK1.8使用的是2次幂的扩展(指长度扩展为原来的2倍),所以,元素的位置要么在原位置上,要么是在原位置再移动2次幂的位置。