HashMap的数据结构
底层由数组和链表组成,数组是主体,链表是为了解决Hash冲突,jdk1.8后,当链表的长度大于阈值8时,会转换为红黑树java
HashMap的hash函数实现原理
一、JDK 1.8 中,经过key的hashCode()方法获得值的高16位异或低16 位实现的:程序员
(h = k.hashCode()) ^ (h >>> 16)
二、使用计算获得的hash值与数组长度n-1作与位运算获得数组下标:数组
if ((p = tab[i = (n - 1) & hash]) == null)
计算下标时为何用位与运算?安全
- 因为位运算直接对内存数据进行操做,不须要转成十进制,所以处理速度很是快
- 正整数对2的倍数取模,只要将数与2的倍数-1作位与运算
- 对2的倍数取余,只要将数右移2的倍数位
为何使用异或运算?
这段代码叫“扰动函数”,目的是为了混合原hash码的高位和地位,混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相的保留下来session
- 计算获得hash值后须要与数组长度-1作与位运算获得元素所在数组下标位置,此时数组长度-1至关于一个“低位掩码”,结果是hash值的高位所有归零,只保留低位值
- hashCode函数返回的int值范围为32位,右移16位再异或,至关于本身的高半区和低半区作异或,这样获得的hash值混合了原始hash码高位和低位的特征,减小了hash碰撞的概率
为何用异或,不用与和或运算?

图片转自:https://zhuanlan.zhihu.com/p/...数据结构
HashMap的put方法执行过程
- 调用哈希函数获取Key对应的hash值,再计算其数组下标;
- 若是没有出现哈希冲突,则直接放入数组;若是出现哈希冲突,则以链表的方式放在链表后面;
- 若是链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;
- 若是结点的key不存在,直接插入,若是已经存在,则替换其value;
- 若是集合中的键值对大于12,调用resize方法进行数组扩容。
HashMap的容量与扩容机制
初始容量与负载因子:函数
扩容过程:spa
- 建立一个新数组,其容量为旧数组的两倍
- 将原数组中的元素迁移至新数组。jdk1.8如下须要从新计算下标位置,jdk1.8后作了优化,只需计算hash & oldCap的值便可肯定元素在新数组中的位置(oldCap为原数组长度),分如下两张状况:
A:扩容后,若hash值新增参与运算的位=0,那么元素在扩容后的位置=原始位置
B:扩容后,若hash值新增参与运算的位=1,那么元素在扩容后的位置=原始位置+原数组长度。

因此说扩容是一个特别耗性能的操做,因此当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大体的数值,避免map进行频繁的扩容。
为何使用须要链表转红黑树
- 引入红黑树就是为了查找数据快,解决链表查询深度的问题。链表查询效率为O(n),当长度过长时查询效率慢,红黑树查询效率O(lgn),查询效率快
- 红黑树在插入新数据后可能须要经过左旋,右旋、变色这些操做来保持树的平衡
- 构建红黑树和维护红黑树的平衡也须要一些操做损耗资源,因此在链表长度大于8时才转换成红黑树
红黑树:
- 每一个节点非红即黑
- 根节点老是黑色的
- 若是节点是红色的,则它的子节点必须是黑色的(反之不必定)
- 每一个叶子节点都是黑色的空节点(NIL节点)
- 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
jdk8中对HashMap作了哪些改变?
- 在java 1.8中,若是链表的长度超过了8,那么链表将转换为红黑树。
- 发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入
- 扩容迁移数据时,jdk1.8没有从新计算新数组下标,而是经过hash & 原数组长度来肯定元素在新数组中的下标
- 在java 1.8中,Entry被Node替代(换了一个马甲)。
hash冲突的解决方法
一、开放地址法
当出现hash冲突时,执行左右空节点探测
- 线性探测再散列,当hash值p出现冲突时,则将数据放到
(p + 1) % m处
,逐步向后探测
- 二次探测再散列,当hash值p出现冲突时,则将数据放到
(p + 1) % m
处,若是此时还存在冲突,则将数据放到 (p - 1) % m
处,左右探测
- 伪随机探测再散列,构建一个随机数列探测,di依次取数列中的值
缺点:不能删除节点、只能对节点做删除标记;要求哈希表空间大于或等于装填数据数目。
二、再hash法
构造多个hash函数,当一个冲突时,再计算第二个的hash值,直到不冲突为止
优缺点:这种方式不容易产生汇集,但增长了计算时间
三、链地址法
出现冲突时用链表连接
缺点:须要额外的空间
四、创建公共溢出区
将哈希表分为基本表和溢出表两部分,凡是和基本表中元素发生冲突的元素均存入溢出表
ConcurrentHashMap数据结构
jdk1.7:
- 使用一个Segment数组和多个HashEntry数组组成
- 当执行put操做时,会进行第一次key的hash来定位Segment数组的位置,若是该Segment尚未初始化,即经过CAS操做进行赋值,而后进行第二次hash操做,找到对应的HashEntry的位置,再经过ReentrantLock进行加锁后,将数据添加到链表尾部
jdk1.8:
- 与HashMap结构一致,底层是数组+链表或数组加红黑树的结构实现,当链表的节点数大于8时会转换为红黑树
- 操做流程是,对key进行hash运算定位到数组下标,若是下标位置table为空则先初始化,再cas插入,若是有数据,则用同步锁Synchronized进行加锁后插入
jdk1.7数据结构图:

HashMap,LinkedHashMap,TreeMap 有什么区别?
- HashMap 经过hash函数计算数组下标,key是无序的
- LinkedHashMap经过双向链表保存了记录的插入顺序,在用 Iterator 遍历时,先取到的记录确定是先插入的;遍历比 HashMap 慢;
- TreeMap 实现 SortMap 接口,可以把它保存的记录根据键排序(默认按键值升序排序,也~~~~能够指定排序的比较器)
HashMap、TreeMap、LinkedHashMap的使用场景
- HashMap:通常状况下,使用最多的是 HashMap。只须要普通的在 Map 中插入、删除和定位元素时;
- TreeMap:在须要按天然顺序或自定义顺序遍历键的状况下;
- LinkedHashMap:在须要输出的顺序和输入的顺序相同的状况下。
HashMap 和 HashTable 有什么区别?
- HashMap 是线程不安全的,HashTable 是线程安全的;
- HashMap容许插入null键,HashTable不容许;
- HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;
- HashMap 须要从新计算 hash 值,而 HashTable 直接使用对象的 hashCode