HashMap
HashMap基于hashing原理,我们通过put()和get()方法存储和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来存储值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。
HashMap中默认的初始容量为16,默认的加载因子为0.75。
- 初始容量代表了哈希表中桶的初始数量,即 Entry< K,V>[] table 数组的初始长度
- 加载因子是哈希表在其容量自动增加之前可以达到多满的一种饱和度百分比,其衡量了一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。
- 比如加载因子是0.75,当数组的使用的长度超过该数组长度*0.75的时候,该数组便会自动扩容。table在HashMap扩容的时候,长度会翻倍。
所有的数据都是通过一个Node节点进行封装,其中Node节点中封装了hash值,key,value,和next指针。hash是通过key计算出的hashCode值进行对数组容量减一求余得到的(官方的求余方式是通过&运算进行的)。不同的key计算出来的hash值可能相同,解决冲突是通过拉链法(链表和红黑树)进行处理。正是因为这种存储形势,所以HashMap的存取顺序是无序的。
在JDK1.8之后,HashMap底层是由“数组+链表+红黑树”组成。
当要存储一个键值对时,就会先计算键的哈希值(hashCode),然后对其进行取余操作。比如这个数组的长度为16,某个键的哈希值为17,那么17%16=1,所以这个键值对应该存在数组下标为1的位置。那有一个问题,假如某个键的哈希值的33,33%16也是等于1的,那这样子该怎么存呢。其实数组中的每个元素都是一个链表,也就是数组的某个位置可以存放多个值。当链表中的数据量大于8时,链表就会自动转换成红黑树,当红黑树中的数据量小于6时,红黑树又会自动转换成链表。如下图:
HashMap的具体实现步骤如下:
1、通过调用put(K key, V value), 利用hash(key)计算哈希值(存储位置)。
2、判断Node数组table是否为null或等于0
- 如果是,调用resize()方法对数组进行扩容。
- 如果不是,判断table[i]是否已经有元素。
1)没有元素。new Node直接插入
2)有元素。判断传入的key是否存在。
a、若存在,则用新值覆盖旧值。
b、不存在,判断table[i]是否为treeNode(红黑树节点)
- a)是,红黑树直接插入。
- b)不是,遍历链表,判断长度是否大于等于8
- 是,转为红黑树插入
- 不是,转为链表插入。key存在则覆盖value。