1.基本概念:
它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,遍历顺序不确定。最多允许一条记录的键为null,允许多条记录的值为null。非线程安全,若要满足线程安全,可以用Collection的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
2.底层实现:
数组+链表+红黑树(jdk1.8增加了红黑树)
(1)Node是HashMap的一个内部类,实现了Map.Entry接口,本质就是一个映射。
(2)HashMap采用链地址法解决hash冲突。
(3)Hash算法与扩容机制
Node[] table的初始化长度length(默认16),LoadFactor为负载因子(默认0.75),threshold是HashMap所能容纳的最大数据量的Node个数。threshold = length * LoadFactor。 数组长度确定后,负载因子越大,所能容纳的键值对个数越多。超过threshold这个值就要重新扩容。size指的是HashMap中实际存在的键值对数量。modCount记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败,相同key的value被覆盖不属于结构变化。
为了避免出现拉链过长的情况,jdk1.8之后,对数据结构做了进一步优化,引入了红黑树,当链表长度太长(默认超过8)时,链表就转化为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能。
hash算法:
三步:取key的hashCode值、高位运算、取模运算
对于任意给定的对象,只要它的hashCode()返回值相同,那么程序调用方法一所计算得到的Hash码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,模运算的消耗还是比较大的,在HashMap中是这样做的:调用方法二来计算该对象应该保存在table数组的哪个索引处。
这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。