1、HashMap长度为什么是2的n次幂?
1)节省空间
2)减少哈希碰撞概率
2、HashMap如何解决哈希冲突?
采用链表
3、HashMap在多线程中使用会出现什么问题?如何解决
1)多个线程put可能导致元素丢失
//关键点1
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//关键点2
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
举例:
线程A和线程B同时调用put方法,都走到关键点1处,都判断当前节点为空,线程A先执行创建新节点,之后线程B执行创建新节点会覆盖上一个已添加的元素,导致元素丢失。
关键点2同样的原理
2)put和get并发执行可能导致get为空
如果put时正好遇到需要扩容,扩容的过程中会新创建一个节点数组,然后再把数组搬运到新数组中。如果线程B此时调用get,那么获取到的元素即为空。
//关键点
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//赋值给全局变量
table = newTab;
解决方案:
1)对HashMap的操作加锁
2)使用ConcurrentHashMap替代
3)使用Collections.synchronizedMap(hashMap)
4、HashMap什么时候会扩容?
1.8: put元素后超过阀值 或 链表超过8且长度小于64
5、Java1.8 HashMap为什么使用链表+红黑树?
链表的查找效率为O(n),而红黑树的查找效率为O(logn),查找效率变高了。
红黑树的查找效率虽然变高了,但是插入效率变低了,如果从一开始就用红黑树并不合适。从概率学的角度选了一个合适的临界值为8。
在源码的注释中也给出了相关原因:
Because TreeNodes are about twice the size of regular nodes, we
use them only when bins contain enough nodes to warrant use
(see TREEIFY_THRESHOLD)
因为树节点的大小是链表节点大小的两倍,所以仅在包含足够数量节点时才使用它。这个数量就是8。
6、HashMap在JDK1.7和1.8的区别?
1.7:
1)构造方法中会初始化一个EmptyTable
2)hashMap由数组+链表组成
3)对于哈希碰撞,采用头插入法
4)哈希算法不同
5)扩容时迁移数据计算节点角标方式不同hash & (length-1)
1.8:
1)无,仅对负载因子和阀值变量赋值
2)hashMap由数组+链表+红黑树组成
3)对于哈希碰撞采用尾插入法(避免了死循环)
4)哈希算法采用高16位和低16位异或(异或:不同为1,相同为0)
5)扩容时迁移数据计算节点角标hash & oldCap,为0放在原位置,为1放在i+oldTable.length
7、hashcode和equals的区别?
hashcode:将任意一个对象根据指定的算法转为32位int
equals:比较两个对象是否相等
hash值相同,对象并不一定相同,equals相同,hash值一定相同
8、HashMap是否允许存放null?
允许,key、value都可以,放在第一位