HashMap源码
HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。
我们都知道数组中添加数据,要根据数组下标进行添加,那我们怎么获取下标呢?
通过Entry中的key获取要添加的数组下标index
1.如果直接使用key进行hash算法,得出的结果很难作为下标
System.out.println("1111111111111111".hashCode()); //-1225568000
怎么解决?
可以将index = HashCode(Key) % Length。但是取余的效率很低在HashMap中没有使用。
在HashMap中使用位运算更高效获取index index = HashCode(Key) & (Length - 1)
i = (n - 1) & hash //使用与运算更高效,化为二进制进行与运算只有对应位置全为1结果才是1
但是怎么才能确保与运算出来的结果均匀分布数组下标
在HashMap数组的长度通常固定为16或2的幂 为什么?
1.例如:数组长度(length)为 :16
“1”的hash值是49对应的二进制为 0011 0001
数组长度(length)为 :16
(length-1)=15 对应的二进制为0000 1111
”1"的hash值 0011 0001
15 0000 1111
& _________ 求出index=1 ,因为15的高四位全是0 所以与运算求出的index0~15
0000 0001 之间。正好对应数组下标。
2.假如:数组长度(length)为:10
”1"的hash值 0011 0001
9 0000 0101
& _________ 这时候对应的与运算结果只有 0101,0001 ,0100 不会均匀分布数下
0000 0001 标
但是对于数组长度为16的HashMap不管hash值怎么变都是后四位进行与运算,很大概率出现index相同导致链表过长。这使hash值多出的高位没有任何意义。
所以要想办法让高位参与运算
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
扰动函数
为了解决这个问题,在hash方法中又给hashCode添加了一次运算,俗称“扰动函数”。其实本质是把hasCode的也高位拉进来,一起参与运算。hashCode返回的int类型有32位,正好一分为二,划分为高16位和低16位。每次得到hashCode值之后,强行将高16位同低16位先进行一次运算,这样就能够解决hashCode不松散或者等差数列的问题。
这个时候就又出现了另一个问题,为什么要用异或运算而不是其他位运算呢?
- 如果采用与&运算,那么高位就会被0与掉,运算结果整体就会向0方向偏移;
- 如果采取异或^运算,运算结果整体就会向另一个方向偏移;
而异或运算,则是“不同为1,相同为0”,具有更多的随机性和分布性。
右移16位异或可以同时保留高16位于低16位的特征
"1314".hashCode() 1 0111 0000 1011 1100 0101
"1314".hashCode()>>>16 无符号右移 1 0111
^(按位异或相同为0不同为1) __________________________
1 0111 0000 1011 1101 0010
得到的hash值 1 0111 0000 1011 1101 0010
15 1111
& __________________________
0010
这样的运算得到的index散列行更强不至于出现一个下标中链表过长,从而降低hash碰撞的概率。
产生hash冲突
链表解决
将hash冲突的数据使用链表的形式进行尾插法(1.7使用头插法)
数组扩容
(JDK1.8为例)
如图
先知道三个概念
table:存储HashMap节点信息,是个数组
slot:hash槽,即table[i]
bullet:hash桶,相同hash槽上所有元素的集合
这张图有点像一张二维表。HashMap的数据结构采用是分而治之的思想,按照一定的规则将数据一排排存放,查找的时候先找到数据在哪一排,然后在从这排数据挨个找。提高效率。
那么每一排的数据是怎么存放的。以链表的形式存储的,并且满足一些条件时,链表会转化为红黑树。当然,红黑树也会再变成链表。
HashMap简单来说是底层数据结构是数组+链表+红黑树的组合,
数组记录着每一列的位置,链表和红黑树表示列上元素的排列规则。
在源码中,用table表示数组,代码如下
transient Node<K,V>[] table;
Node是HashMap的内部类,是链表上的节点。里面的元素有hash值,键值对以及指向下一个节点的next指针,代码如下:
static class Node<K,V> implements Map.Entry<K,V>{
final int hash;
final K key;
V value;
Node