bigint最大有多少位_没人比我更懂HashMap(HashMap&位运算)

55d0d97cb4ad48d1e8f49c1b653d7c0b.png

一、HashMap

为什么面试官这么喜欢问HashMap

  • HashMap在实际开发中使用较多
  • 考察你是否有钻研精神,是否会对常用的只是会用,还是会去了解原理
  • HashMap知识面较广,涉及位运算、算法、数据结构等,容易看出一位面试者水平
  • ……

版本区别

  • 1.8之前 在jdk1.7中,首先是把元素放在一个个数组里面,后来存放的数据元素越来越多,于是就出现了链表,对于数组中的每一个元素,都可以有一条链表来存储元素。这就是有名的“拉链式”存储方法。
  • 1.8之后 由于存储的元素越来越多,链表也越来越长,在查找一个元素时候效率不仅没有提高(链表不适合查找,适合增删),反倒是下降了不少,于是就对这条链表进行了一个改进。如何改进呢?就是把这条链表变成一个适合查找的树形结构,没错就是红黑树。值得注意的是,因为需要为了退化成链表和遍历做准备,这个红黑树并不是纯红黑树,而是红黑树和双向链表的叠加结构。

运行流程

a12c1e0a45ef99d13d50c99a8a7c56af.png

二、数据结构

HashMap的数据结构采用了“用空间换时间”的思想来保证综合效率。

哈希表

哈希表是一个通过数组和链表相结合而成的数据结构,既避免了数组的增删慢,也避免了链表查询查询慢的的缺陷。

  • 插入:通过hash算法计算在数组中的位置,然后插入。如果该位置已有元素,就生成一个链表,使用“头插法”插入元素到链表头(如果插在链表尾,需要先遍历链表,会提升时间复杂度)。
  • 查询:通过索引算法算出位置,再遍历该索引上的链表。
  • 扩容:数组内容超过负载因子,就使用扩容算法进行扩容。

9ad09096a5038c790f11d5501d3ed69a.png

红黑树

红黑树详情

为什么使用红黑树而不使用AVL树(平衡树)

  • 1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
  • 2、平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。

三、位运算

计算机中的数在内存中都是以二进制形式进行存储的,用位运算就是直接对整数在内存中的二进制位进行操作,因此其执行效率非常高,在程序中尽量使用位运算进行操作,这会大大提高程序的性能。而HashMap中也是使用位运算来实现算法的。

二进制转换

转换十进制和二进制非常简单,我们先用十进制的1234和二进制的1011来举例,看看两者的区别

十进制的位表示的倍数:
1(10的3次方)2(10的2次方)3(10的1次方)4(1)
1234=1*10的三次方+2*10的2次方+3*10+4*1

二进制的位表示的倍数:
1(2的3次方)0(2的2次方)1(2的1次方)1(1)
1011=1*2的三次方+0*2的2次方+1*2+1*1

二进制转换十进制:
1011=8+0+2+1
1011=11

十进制准换二进制:

1234/2=617, 余数为0

617/2=308,  余数为1

308/2=154,  余数为0

154/2=77,   余数为0

77/2=38,    余数为1

38/2=19,    余数为0

19/2=9,     余数为1

9/2=4,      余数为1

4/2=2,      余数为0

2/2=1,      余数为0

1/2=0,      余数为1

再从下往上数得出1234的二进制:10011010010

位运算符

  • & 与运算 两个位都是 1 时,结果才为 1,否则为 0
    1 0 0 1 1
&   1 1 0 0 1
————————————————
    1 0 0 0 1
  • | 或运算 两个位都是 0 时,结果才为 0,否则为 1
    1 0 0 1 1
|   1 1 0 0 1
————————————————
    1 1 0 1 1
  • ^ 异或运算,两个位相同则为 0,不同则为 1
    1 0 0 1 1
^   1 1 0 0 1
————————————————
    1 0 1 0 1
  • ~ 取反运算,0 则变为 1,1 则变为 0
~   1 1 0 0 1
————————————————
    0 0 1 1 0
  • << 左移运算,向左进行移位操作,高位丢弃,低位补 0
9 << 3;
移位前:0000 0000 0000 0000 0000 0000 0000 1001
移位后:0000 0000 0000 0000 0000 0000 0100 1000
  • >> 右移运算,向右进行移位操作,对无符号数,高位补 0,对于有符号数,高位补符号位
9 >> 3;
移位前:0000 0000 0000 0000 0000 0000 0000 1001
移位后:0000 0000 0000 0000 0000 0000 0000 0001

四、算法

在HashMap中所有算法均使用的位运算,位运算的效率比运算符更高,除了省下二进制与十进制转换的时间,JVM在底层也对位运算进行了优化。

索引算法

计算索引的代码:

int index=h&(INITIAL_CAPACITY-1);
    //假设INITIAL_CAPACITY为16
    1001 1011 //155
   &     1111 //16-1
   ——————————
         1011   //直接舍弃前面N位,取后4位,特别的高效

将hash值与阈值进行位运算获得数组中的索引,当一个int值a是二的次幂的时候,h跟a-1进行与运算的时候,刚好是h % a,这是也是为什么HashMap的数组大小需要为2的整次幂的原因之一,也是导致hash冲突的原因(可能2个索引在一个位置)。

HashCode算法

在HashMap中采用了下图进行HashCode的运算,这样可以将hashCode的前后16位都充分利用。并且使用了异或运(其他的离散值为3/4,而异或为1/2)算来加大离散度(在哈希计算中,所有的操作都是为了加大离散度)。

h = hashCode ^ (hashCode >>> 16) //扰动函数
  • 为什么已经有HashCode了,还要进行一次运算呢?

如果直接用HashCode来进行索引算法的话,那进行运算的无论前面的怎么变,只有后四位参与运算,这样就会产生大量的Hash碰撞

…… 1111 1111 0000       //任意结尾为0000的数与1111进行计算
…… 0111 0000 0000
…… 0000 0000 0000
&            1111
——————————————————
             0000

根据上面的十进制转二进制算法,只要任何数,前4次除以2余数为0(例如大数值的偶数),都会碰撞,数组都给你撞烂。如果使用扰动函数进行扰动呢?

1001 0110 1111 1010         //前面缩略,总是就是前半部分和hashcode进行位运算
^         1001 0110
———————————————————
          0110 1100   
&              1111
——————————————————
               1100

如果还是跟1111与运算的话,一下就有8个数字参与了运算,如果再扩容一次,和11111运算,就有10个数字参与运算,大大减少碰撞几率。

扩容算法

  • 初始化容量多少合适?

initialCapacity(需要存的元素个数/0.75)+1,默认为16。

  • 为什么扩容大小要求为2的整次幂?

如果length为2的次幂 则length-1 转化为二进制必定是11111……的形式,在与hashCode的二进制与操作效率会非常的快,而且空间不浪费;如果length不是2的次幂,比如length为15,则length-1为14,对应的二进制为1110,在与hashCode与操作, 最后一位都为0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费

  • 扩容算法是如何判定一个数是否需要移动到新位置
e.hash & oldCap == 0
//假设oldCap==16,现在有两个数都在同一个索引上
1111 1111
0000 1111
&    1111
—————————
     1111	//原本都在1111这个位置上,但是经过一次扩容

1111 1111
0000 1111
&  1 0000		//跟oldCap进行与运算,判断是否该移动
———————————
   1 0000		//第一个结果不等于 0 则不需要移动
   0 0000		//第二个结果等于0需要移动
  • 最大容量为多少

因为计算采用int值,int值最大为2的31次-1,达不到2的31次,所以为2的30次。

  • 为什么负载因子是0.75

根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值