面试必问的HashMap-1.1:HashMap几个关键点分析

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

 

 

猜你喜欢

转载自blog.csdn.net/u010953880/article/details/88529363