HashMap总结归纳
1.HashMap底层是如何实现的?
JDK1.7:数组+链表
JDK1.8: 数组+链表/红黑树
2.HashMap的长度为什么是2的幂
1.8和1.7对比之后发现都是2的幂,因为对key进行hash运算之后,将key映射到哈希桶数组位置,需要取模运算hash%length,而如果是2的幂,hash & (lenth-1)正好等同于hash % length,但是位运算比取模效率更好。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
1.7:
static int indexFor(int h, int length) {
return h & (length-1);
}
3、JDK1.8计算hash为何采用hashcode与向低位移动16位再按位异或
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
因为如果高位16位不同,低位16位相同,计算下标的时候会相同,所以避免分布不均匀,jdk1.8采用这种方式使得key的hashcode尽量不同,分散的均匀。
4.Jdk1.8中满足什么条件后将链表转化成红黑树?
在putVal方法中是判断桶内的某一index挂的节点个数是否大于8,大于则转为红黑树。一旦转换成红黑树,即时以后删除数据也不会还原成链表,至于为何是8,是因为根据泊松分布,在负载因为为0.75的情况下,单个hash槽内节点下面的元素个数为8的概率小于千万分之一,所以将7作为一个分水岭,等于7不转换,大于等于8才进行转换。
5.>>> JDK1.7链表使用头插法,为啥JDK1.8改成了尾插法,因为多线程同时扩容(rehash)情况下,jdk1.7 resize 头插法会容易出现死循环,所以改成尾插法,一定程度上避免了死循环;
>>> 并发情况下最好不要使用hashmap,因为即使jdk版本>=1.8也会出现其他原因的死循环,建议使用并发容器ConcurrentHashMap。
6、 HashMap就是使用哈希表来存储的。哈希表为解决冲突,可以采用开放地址法和链地址法等来解决问题,Java中HashMap采用了链地址法
7、在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”
jdk7: #这里拿新容量重新计算了一次 int i = indexFor(e.hash, newCapacity); static int indexFor(int h, int length) { return h & (length-1); } jdk8:
#这里直接和原来的cap按位与原来的cap,看看新增的bit是0还是1 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; }