学的会一些HashMap知识和疑惑

在日常开发中使用过的java集合类有哪些?
一般应聘者都会回答ArrayList,LinkedList,HashMap,HashSet等等。如果连这几个集合类都不知道,基本上可以pass了。(这几个都要找来看看源码解析和常用的方法)

记住数组的长度是不能改变的,扩容也只是在做数据的迁移

只能有一个K为null值的元素,可以有多个V值为null的元素

1.jdk7那个内部类叫entry,jdk8叫node

2.jdk8用的红黑二叉树,在一条链表上元素大于8个时(就是9个或以上),转为红黑二叉树,在元素少于或者等于6个时转为链表,自平衡红黑二叉树,解决了单纯链表在hash冲突严重时,链表过长,查询效率太低的情况,红黑二叉树的查找次数,不会大于树的高度.

自平衡红黑二叉树的建立规则,变色,左旋,右旋

3.jdk7在扩容时,是重新对k进行hash操作,然后对新扩容后的数组长度取模来获取index,其实这是浪费性能的,因为哈希桶的长度都总会是2的N次放,所以在jdk1.8优化了,只需要看看原来的hash值新增的那个高位bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”(还是没搞明白怎么优化,不重新计算出来那怎么去看呢,还说什么新增的那个高位bit是1还是0,可认为是随机的,那存储位置是原索引还是原索引加oldCap也是随机的了,那要取的时候怎么取呢(也随机在这2个位置取,1个位置没取到,再到另一个位置取??))

4.jdk7扩容时链表会倒序,而1.8不会

支持:

1.取模运算用位与运算来优化(是用数学公式换算得来),大大加快计算时间(计算机操作位与更快)

2.hash操作,这里用的先取得K值的hashcod值后,(疑问:hashcode值都是一个32位的二进制数吗??),做位运算>>16,右移16位,(相当于是除以2的16次方的意思),然后再与原hashcod值进行异或运算,得到一个最终数,然后再取模运算得到index(不用hashcode原值直接进行取模运算,而要这样做的原因是:主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。)

//hash算法我们一般说的就是hashcode()方法,这是可以被重写的方法(继承自Object),所以有很多种hash算法,,hashCode()不要求唯一但是要尽可能的均匀分布,而且算法效率要尽可能的快。如果面试者能回答出一些常用的算法,比如MurMurHash(萌萌哒的名字)基本上已经可以俘获面试官了。

3.源码中modcount记录的是数据结构改变的次数(增加,删除等这些操作都会改变数据的结构,但是覆盖不算),适用于快速失败的(还没搞懂这个意思)

4.size是记录的HashMap中存在多少个元素,而不是记录的有多少个桶中有元素,空参初始化的map有16个空桶,但size是0.

5.threshold阀值,等于哈希桶的长度x负载因子(可以增加到1,但一般不动,负载因子太小,就说明会频繁扩容,消耗性能和内存,但相对来说,Hash碰撞减少,查取删效率提高,负载因子过大,节省了内存,降低扩容次数,增加hash碰撞几率,查取删效率降低)

6.k为null值的数据,是存储在index为0的哈希桶里(要么就是在头部,要么就是在链表上,这是源码中就这么存K为null的元素的)(问题:null的hashcode值是0吗,null因该是没有hashcode值吧,null没法调用hashcode()方法,这是要空指针的?)

7.解决hash碰撞的方法有那些(不单只是说hashMap里):开放定址法、链地址法(也叫开链法,HashMap用的方法)、再哈希法。

8.HashMap怎么个线程不安全,====

9.怎么让它变为安全的,Collection.synchronizeMap(map)方法,可以变把HashMap变为安全的,或者用ConcurrentHashMap是线程安全的,也可以用Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但一般不用它,因为它为了多线程安全,对整个数据进行了锁定,效率低,而我们这时为了线程安全,会用ConcurrentHashMap,它使用了分段锁,并不对整个数据进行锁定,安全的同时保证一定的效率.

10.为什么HashMap的哈希桶长度,要选择为2的幂次方,并且初始化为16,而不是8也不是32,也不是15也不是17,有博文,首选是哈希桶的长度我们在new一个HashMap的时候,我们是可以手动指定,比如我们指定为15,但在源码中,对象初始化的时候,还是会为我们选择大于15的,但又最接近15的2的幂次方数,所以内部还是变成了16,然后就是一个操作后的哈希数,对一个2的幂次方数进行取模运算,产生的哈希碰撞概率是最小的,也就是说可以更均匀的分散的存数据,降低链表的长度,提高增查删的效率.至于初始为什么为16,明天再来找资料

put操作,做了哪些事情:

1.对k的hashcod做hash操作,然后取模得出它在Hash桶的index位置

2.看该index位置是否有元素,没有就直接插入

3.有,就遍历这条链表(或红黑二叉树),逐个比较hashcode值,有相等的且equals也相等的,就覆盖V值,没有相等hashcode值的话,就准备插入.

4.但在插入前,先判断是否需要扩容,不需要的话

5.就将元素插入这个哈希桶位置,然后将它的next属性指向老的元素

6.如果判断触发扩容,执行扩容操作,新建一个map,哈希桶的数组长度X2

7.遍历哈希桶数组,并且遍历每一个桶的链表(或红黑二叉树),按照新的x2的数组长度重新取模,得到各自新的index位置(要么就是原位置,要么就是原位置加老的数组长度,jdk8对这个算法有优化),jdk7的话,因为put的时候链表就是有序的(一条链表,后put进来的会在头位置),这里是遍历到index头位置后,再按照next属性来逐个遍历这条链表,所以再将这些元素按照新的取模值插入到扩容后的哈希桶中时,顺序就反过来了.(而jdk8不会)

8.执行完这部扩容操作后,才将最新要put进来的元素重新取模,得到它要插入hash桶的index位置

9.如果这个inde位置上没有元素,就直接插入,有,就遍历这条链表(或红黑二叉树),逐个比较hashcode值,有相等的且equals也相等的,就覆盖V值,没有相等hashcode值的话,就将元素插入这个哈希桶位置,然后将它的next属性指向老的元素

猜你喜欢

转载自blog.csdn.net/f45056231p/article/details/82562216