HashMap都不算什么特别的,这里整理下我的理解。
分析HashMap就不得不说下数据结构,
首先Java中有几种数据结构:数组、链表、树
数组的话:查询效率高
链表新增效率高。
牛逼的是,我们的HashMap的数据接口就比较吊了,他会把数组和链表的优势结合起来。
数组:
int[] aa 这就是一个数组
链表:
单向链表,其实就主要是存了一个数据和指向下一个节点的指针。
Class Node{
Object data;
Node next;
}
双向链表和单链表其实原理是一样的,只不过它可以从左右都可以访问。
Class Node{
Object data;
Node prevoius; //指向前一个节点
Node next;
}
那HashMap吊的地方在哪里呢?
体现到代码:
数组的表示:transient Node<K,V>[] table;
transient 瞬间的或临时的,也就是说这个成员变量不参与我们的序列化。
数组默认的大小 : 1 << 4; // aka 16 1向左位移4,也就是2的4次方 就是16
这里有一个考点,为什么不直接写16呢?
我的理解,最终都是二进制的,你写16不是很麻烦吗,那我不如直接写位移运算,底层也是二进制的,而且位运算效率高。
单向链表的表示:
Class Node{
Key;
Value;
Node next;
}
链表的长度也不能无限大,怎么叫做一个上限呢?
static final int TREEIFY_THRESHOLD = 8;
红黑树和链表有他们相对适合他们各自效率的一个节点效率。
static final int UNTREEIFY_THRESHOLD = 6;
就是说如果红黑树的节点数量小于6?
为什么会小于6呢?
因为我们可能会对数据进行删除,当不停的删的时候,就有可能导致红黑树的节点数量小于6.
此时,小于6的时候,就把红黑树转成链表。
分析put方法:
(1):put
putValue()
(2)putValue()
hash(key)
(3)为什么需要这个hash算法
因为我需要知道这个key value 到底存在hashMap结构中的哪个位置。
根据key的值去计算(因为key唯一)
首先这里得先说明一下一些异或算法:
异或运算(^):如果两个值不同,则该位结果为1,否则为0。
特点:与0相异或,保留原值 ,10101110 ^ 00000000 = 1010 1110。
与运算(&):两位同时为“1”,结果才为“1”,否则为0
或运算符(|):只要有一个为1,其值为1
hash函数:(h = key.hashCode()) ^ (h >>> 16)
右移16位,就是充分的把我们int类型的32位数全部应用起来。
把该数的高16位 异或 它的低16位 运算起来,这样就把这个数充分的利用了。
其中int类型在java中是32位,不足32位的,高位补0.
右移16位:基本上就是把低位给挤没了。(因为右移的话,把整体右移,低位就没有了)
异或运算之后,得到一个值A,就是知道我这个数保存在数组中的那个位置了。
但是你能保证这个A值在数组中不越界吗?
因为我们数组的初始化大小是16。
就是这个hash函数得到的一个值,它并不是真正确定我们数组下标的一个值,(可能会越界)
一个点:计算数组中的位置
tab[i = (n - 1) & hash]
要保证下标的位置不能超过数组的最大值n(16),因为数组是从0开始计算的,所以是n-1(15)
16 是 10000
15 就是 01111
做与运算的话,就是你前面不管是什么数据的话,你和我进行一个与运算,等于说把你前面的那些数据全部砍掉。
(因为与运算,两个同时为1,结果才为1)
和15做与运算,15的高位补0(共32位)
因此和15 (01111) 与运算之后的有效位只有最后这4位,而这四位的最大值只有15,
因此我们的hash值 与 上一个我们的 n-1 之后,就绝对不会越界了。
注意:
其实与运算相当于取模,相当于模16,因为模运算没有我们的与运算效率高。
(模运算 % (就是取余数))
另一个点:
(n-1) 也是一个很好的地方
为什么不用n呢?
因为官方说了,数组的长度都是2的n次幂。
凡是2的n次幂,它的二进制最高位都是1, 10000000, 10000, 100
一但它减掉个1,除了第一位,其他所有为都是1, 0111111111 , 01111, 011
这样的话,就会有一个好处:
比如我的hash值是:0101
我 与运算 一个0111,就保留了它的位数。(因为与运算同时为1才为1)
如果是 0101 与 0000,这样的话,不论你hash值是0还是1,他的与结果都是0(&运算,同为1才为1)。
结果基本上都是一个值,就有可能落到同一个点,碰撞概率就大了。
如果用n-1的话,这样数组的散列性就大了,碰撞概率就低了。
这么做可以在n 比较小的时候,保证高低 bit 都参与到 hash 的计算中,同时位运算不会有太大的开销。
所以要问HashMap中hash函数怎么实现的?
key的hashcode值,
高16bit不变,低16位和高16位做了一个异或。
(n-1)& hash 得到下标(数组的下标)(这里n就是数组的初始化大小16)
如果还是产生了频繁的碰撞,会发生什么问题呢?
jdk1.8使用树来处理频繁的碰撞。
Java 8中,利用红黑树替换链表,当链表长度大于8时,就使用红黑树这种数据接口。
这样在n很大的时候,能够比较理想的解决这个问题。
求模运算(%)和按位与运算(&)
https://blog.csdn.net/u014266077/article/details/80672995