hashmap是什么
hashmap是一种基于哈希表的数据结构,它可以存储键值对,通过计算键的哈希值来快速定位值的位置。
hashmap的特点
- 支持快速查找:hashmap可以在O(1)的时间复杂度内查找键对应的值,因为它只需要计算键的哈希值并在哈希表中找到对应的槽位。
- 支持动态扩容:hashmap可以根据元素的数量和负载因子来自动调整哈希表的大小,以保证性能和空间效率。
- 不保证有序性:hashmap不保证元素的插入顺序和遍历顺序一致,因为哈希函数可能会导致元素在哈希表中分布不均匀。
hashmap的实现原理
- 哈希函数:hashmap使用一个哈希函数来将键映射到一个整数范围内,然后对哈希表的大小取模,得到键在哈希表中的槽位索引。
- 哈希冲突:当两个或多个键具有相同的哈希值或槽位索引时,就会发生哈希冲突。hashmap使用链地址法来解决哈希冲突,即在每个槽位上维护一个链表,将具有相同槽位索引的键值对存储在链表中。
- 扩容机制:当元素的数量超过哈希表的容量乘以负载因子时,就会触发扩容机制。扩容机制会创建一个新的更大的哈希表,并将旧的哈希表中的所有元素重新计算哈希值并插入到新的哈希表中。这个过程叫做rehashing。
hashmap的源码分析
定义变量
- table:一个Node数组,用来存储键值对节点。Node是一个内部类,包含了key, value, hash, next四个属性。
- size:一个int变量,用来记录元素的数量。
- threshold:一个int变量,用来记录扩容的阈值。它等于table.length乘以loadFactor。
- loadFactor:一个float变量,用来记录负载因子。它是一个0到1之间的数,表示元素数量和容量之间的比例。
- modCount:一个int变量,用来记录结构修改的次数。结构修改指改变元素数量或者改变内部结构(如rehashing)的操作。它主要用于迭代器中检测并发修改异常。
构造方法
hashmap提供了多个构造方法,其中最常用的是无参构造方法和指定初始容量和负载因子的构造方法。无参构造方法会创建一个默认大小为16,负载因子为0.75的空hashmap。指定初始容量和负载因子的构造方法会根据参数创建相应大小和负载因子的空hashmap,并计算出相应的扩容阈值。
put方法
:put方法是向hashmap中添加或更新键值对的方法。它接受两个参数key和value,并返回旧值或null。put方法的主要步骤如下:
- 计算哈希值:put方法会调用hash方法来计算key的哈希值。hash方法会对key的hashCode进行高低位异或,以减少低位冲突的概率。
- 检查容量:put方法会检查table是否为空,如果为空,就调用resize方法来初始化table。resize方法会根据threshold来创建一个Node数组,并将threshold赋值为table.length乘以loadFactor。
- 定位槽位:put方法会根据哈希值和table.length取模,得到key在table中的槽位索引i。然后获取table[i]的节点p。
- 插入或更新节点:put方法会判断p是否为空,如果为空,就直接在table[i]处创建一个新的节点。如果不为空,就判断p的key是否和传入的key相等,如果相等,就更新p的value,并返回旧值。如果不相等,就判断p是否是一个红黑树节点,如果是,就按照红黑树的规则插入或更新节点。如果不是,就遍历p的链表,查找是否有和传入的key相等的节点,如果有,就更新该节点的value,并返回旧值。如果没有,就在链表尾部插入一个新的节点。
- 检查链表长度:put方法会检查链表的长度是否超过了一个阈值TREEIFY_THRESHOLD(默认为8),如果超过了,就调用treeifyBin方法将链表转化为红黑树。treeifyBin方法会先判断table的长度是否小于一个阈值MIN_TREEIFY_CAPACITY(默认为64),如果小于,就调用resize方法扩容table。如果不小于,就创建一个红黑树节点数组,并将链表中的节点复制到数组中。然后遍历数组,按照红黑树的规则插入节点,并将根节点赋值给table[i]。
- 增加元素数量:put方法会将size加一,并检查size是否超过了threshold,如果超过了,就调用resize方法扩容table。
- 增加修改次数:put方法会将modCount加一,并返回null。
get方法
:get方法是从hashmap中获取键对应的值的方法。它接受一个参数key,并返回value或null。get方法的主要步骤如下:
- 计算哈希值:get方法会调用hash方法来计算key的哈希值。
- 定位槽位:get方法会根据哈希值和table.length取模,得到key在table中的槽位索引i。然后获取table[i]的节点p。
- 查找节点:get方法会判断p是否为空,如果为空,就返回null。如果不为空,就判断p的key是否和传入的key相等,如果相等,就返回p的value。如果不相等,就判断p是否是一个红黑树节点,如果是,就按照红黑树的规则查找节点,并返回其value或null。如果不是,就遍历p的链表,查找是否有和传入的key相等的节点,如果有,就返回其value。如果没有,就返回null。
resize方法
resize是hashmap的扩容方法,它的主要步骤如下:
- 创建新的哈希表:resize方法会根据table的长度来计算新的哈希表的长度。如果table为空,就使用默认的初始容量16。如果table已满,就将长度翻倍,但不能超过最大容量MAXIMUM_CAPACITY(默认为2^30)。然后创建一个新的Node数组,并将新的阈值赋值为新的长度乘以负载因子。
- 复制旧的元素:resize方法会遍历旧的table中的所有节点,并将它们复制到新的table中。复制过程中,会根据节点的哈希值和新的长度取模,得到节点在新的table中的槽位索引。如果节点是一个红黑树节点,就调用split方法将红黑树分裂为两个链表,并分别插入到新的table中。如果节点是一个链表节点,就直接插入到新的table中。
- 替换旧的哈希表:resize方法会将旧的table引用替换为新的table引用,并返回新的table。