HashMap
HashMap实现了Map接口,用于存储K,V键值对的集合类。
主要特征包括:Key可存NULL、无序、线程不安全、 底层使用了数组+链表+红黑树的结构。
特点
在阅读源码基础上,查阅并结合其他文章资料,以介绍+提问的形式,列出HashMap以下常见特点若干项。
-
HashMap存储单元类型。
hashMap内部使用Node类型对象来保存内容。
Node类型中包括hash、key、value、next四部分。 -
HashMap的存储结构。
HashMap的存储结构中使用了Node类型的数组、单链表、和红黑树。
单链表和红黑树是用来处理哈希冲突时的节点存储。 -
HashMap初始容量、最大容量、负载因子、如何扩容。
初始容量:默认为16。
最大容量:为2^30。
指定容量时,使用tableSizeFor方法向上取值,且未2的幂次方,比如:30向上取值为32。
负载因子默认为0.75,扩展因子*容量为扩容阈值,当达到扩容阈值时,容量扩为原来的2倍。 -
HashMap put 插入过程。
简单划分为以下4个过程:-
初始化检查
如果数组为空,则初始化数据。 -
确定插入位置
散列计算确定插入的哈希桶位置,采用Hash值对N取模【 hash & (N-1)】,确定哈希桶的位置。 -
哈希碰撞、处理阶段
如果要插入的哈希桶为空,则直接插入节点。
如果哈希桶有值了,则判断是哈希冲突还是重复插入。
如果是重复插入,则更新插入的值。
如果是哈希冲突,则采取处理哈希冲突的策略,策略包括拉链法和构建红黑树。 -
容量检查、扩容
插入节点后,如果超出了阈值【大小*负载因子】,进行Resize扩容操作。
-
-
HashMap的扩容过程。
新数组扩容为原来数组的两倍。
将原来数组中节点拷贝到新数组中。如果:- 原节点是普通节点,则用该节点哈希值取模,确定在数组中的位置,插入。
- 原节点是链表节点,则将整个链表拷贝到新数组中。
- 原节点是红黑树,用 split方法将红黑树拆成两个链表,判断每个链表的长度是否小于等于 六,如果是就将 TreeNode 转换成桶内链表,否则再转换成红黑树。
-
HashMap插入数组的位置如何确定。
采用Hash值对容量N取模,模得到的值即是下标值。
在HashMap中采用位运算进行取模:(n-1) & hashCode()。 -
HashMap中如何判断哈希冲突。
当两个Key的HashCode相同,且Key不同时,认定为冲突。 -
HashMap中如何处理哈希碰撞。
当发生哈希碰撞时,首先会采用拉链法处理冲突。
当哈希桶中拉链长度小于等于8时,会采用尾插法插入节点,形成一个单向链表。
当拉链长度大于8,且数组容量【哈希桶】小于64时,会进行扩容,不会进行树化。
当拉链大于8且数组容量大于等于64时,会将链表转换为红黑树。 -
HashMap中取哈希码的逻辑是什么?为什么这样做 ?
HashMap取哈希码采用将高16位与低16位进行异或运算。(h = key.hashCode()) ^ (h >>> 16);
这样的好处是将高位的特征保留到低位中,使低16位相同,高16位不同的32位数在取模时不会得到相同结果,增加随机性、减少冲突。 -
Hash方法取哈希值为什么要使用异或运算 ?
异或运算保证了32位中只要有1位发生变化,高低16位异或得到的哈希值就会改变!!!
与、和运算达不到这样的效果! -
从链表到红黑树转换的值为什么是8 ?
当长度为8的时,红黑树的平均查找长度是Log(8) =3;链表的平均查找长度为8/2 =4,所以有必要从链表转为红黑树。 -
HashMap的容量值为何要设置成2的幂次方 ?
为了便于使用位运算对Hash值取模。例如:在JDK 1.0时 HashTable的取模运算方法是 hash % N ;而在HashMap中,将N固定为2的幂次方时,可以采用 hash & (N-1) 算法完成取模操作,加快运算速度。 -
HashMap是否线程安全 ?
HashMap线程不安全,若要保证线程安全,可以使用ConcurrentHashMap、或使用Collection中synchronizedMap的包装器进行包装。 -
JDK 1.7和1.8中HashMap的主要区别?
JDK 1.8引入并使用了红黑树。
JDK 1.8采用了尾插法插入节点。