淘先锋技术网

首页 1 2 3 4 5 6 7

1.HashMap你了解吧?说一下他的特点

HashMap是以数组+链表以K-V的形式去存储数据的,带有下标索引值,因此HashMap查询块的特点,查询复杂度为O(1),又因为基于hash碰撞得出的链表使得插入的复杂度也为O(1).所以HashMap是数组和链表优点合体的一种数据模式

 2.1.7和1.8的HashMap有什么区别?

1.7HashMap采用的是头插法,数组+链表的形式,1.8采用的是尾插法,数组+链表+红黑树

3.那你说说他的数组+链表是如何实现的呢?

每当put一个元素的时候,它会将key-value封装成一个Node对象,HashMap的数组就会基于这些Node形成数组,链表中的把next指向下一个node对象.

4.为什么要有链表和红黑树,只有数组不可以吗?

  

因为在数组寻找实际位置的时候是采取hash取模得到的实际数组位置,在大量的插入操作时候,这就会产生hash碰撞的情况(也就是两个数经hash取模的位置相同),所有需要引入链表去存储hash后相同位置的元素.

5.常见的解决hash碰撞的算法你了解吗?

a.开放地址法:当发生冲突时,使用某种探查(侦探技术)在散列表中形成一个探查序列,沿此序列逐个单元的查找,知道找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止,查找时探查到开放的地址则表明表中唔待查的关键字,即查找失败.

b.再hash法:意思就是发生hash冲突时先使用一个,如果已经存在有数值, 在重新进行hash,直到无冲突为止.

c.拉链法:HashMap底层就是采取这种解决hash碰撞的方法,每个hash表节点都有一个next指针,多个hash表节点可以用next指针构成一个单向链表,被分配到同一索引上的多个节点用单向链表连接起来

d.建立公共溢出区:将hash表分为基础表和溢出表两部分,凡是发生冲突的元素,一律填入溢出表

6.你说说看hashMap的初始容量,扩容增量,加载因子分别是多少?分别有什么作用

HashMap的初始化容量是16,加载因子是0.75,扩容是原来的一倍 ,当超过16*0.75=12,也就是说当hashmap容量超过12的时候发生扩容的操作,初始容量是决定hash的一个大小,扩容增量则是扩容的一个阈值,加载因子是决定空间和时间的利用,一旦加载因子过大,则填满的元素越多,空间利用率越高,冲突几率加大(因为需要更大的容量才会进行扩容操作,所以元素填满的多,自然进行hash的时候碰撞的几率肯定会上升),反之,加载因子越小,则填满的元素越少,冲突的几率下降,但是更加容易进行扩容,而扩容的空间则会浪费掉了.

7.HashMap的链表长度什么时候转为红黑树?

HashMap碰撞的链表长度大于8时就会发生链表转红黑树的操作

8.那你说说为什么大于8就发生红黑树的操作?6不行吗?7行不行?直接使用红黑树行不行?

因为TreenNodes占用的空间是普通Node的两倍(二叉树相比链表多出了左右指针),所以只有当链表含有足够多的节点才会转为TreeNodes,这是出于时间和空间的权衡,所以当链表大于8时转化为红黑树,当降为6转为普通链表.

当hashCode离散性很好的时候,其实达到链表长度为8的概率非常的小,并且长度为6的链表在查询的速度并不比红黑树来的慢,红黑树一般都是查询数据量多一点的情况(因为每判断一次就减少一半的查询),但是在数据量少的时候,查询优势并不是很明显的时候,显然需要考虑空间,因为使用链表是更好的一种权衡,但是hash碰撞在随机hashcode的情况下,离散性比较差的时候就会导致数据的不均匀,这时候就比较容易产生链表,但是概率还是极低的(0.00000006),几乎为不可能事件,但是在大量存储的时候还是会发生,所以6不能作为判断标准.

而为什么7不行? 是为了做一个缓冲,防止进行频繁的链表和红黑树之间的转换,并且1.8加入红黑树的原因是为了防止链表过长导致查询过慢的问题(时间复杂度由O(n)-->O(logn)).

但是红黑树的空间占用大,并且极少出现,处于性能的考虑不直接使用红黑树,只是作为一个辅助的角色.

 

9.如果默认初始化大小为100,那么元素个数到达75会进行扩容吗?

不会,因为底层源码他是根据容量*0.75 +1, +1是因为小数相除,基本不会是整数,容量大小他是不能为小数的,后面转为int,多余的小数就要被丢掉,所以+1,紧接着调用tableSize(t)方法,这个方法会返回大于t值,并且是离其最近的2次幂,例如t为29,则返回值是32 ,所以是不会进行扩容的,因为初始化它的容量是100,会自动变成128,128*0.75 = 93,  75未到达扩容的情况.

10.HashMap为什么扩容之后数组长度为2的幂次方?

因为这是为了减少Hash的碰撞,尽量使得hash算法的散列结果分布均匀.

11.HashMap是线程安全的么?为什么

hashMap它不是线程安全的,问题发生在resize()方法里面的treansfer中的Entry<k,v>next =e.next;方法中由于1.7采用的是头插法,在进行扩容的时候顺序就会相反

 

当线程1和线程2同时运行时,由于CPU调度线程1比较快一点先执行完扩容 此时链表中的B的指针是指向A的,但就在这个时候线程2开始了扩容刚进行到B,咦,B不是指向C吗? 怎么回事? 为什么B执行的是我自己?那我在获取一下我的下个节点,好险还是B,咦为什么B还是指向的我自己,所以就一直死循环的获取(你就一直while吧,代码稍后给出).最终导致一个死锁的过程.

就是上面这句e.next 不停的获取一下个节点,不停进行循环获取,最终导致死锁.

还有就在多线程操作同一个HashMap的put操作的时候,会产生值覆盖这个问题(即获取到相同的hashcode存储),所以会导致数据冲突问题,造成线程不安全的问题.

12.我想要HashMap线程安全该怎么办呢?

线程安全办法:

1. 使用HashTable,HashTable是线程安全的(不建议使用,就是利用了synchronized进行了加锁)

2.使用并发包下的java.util.concurrents.

3.ConcurrentHashMap,ConcurrentHashMap使用的分段锁可以解决高并发问题

3.使用synchronizedMap()同步方法包装HashMap Object,得到线程安全的Map,并在Map上进行操作.