HashMap是写程序经常使用到的类。
而hashmap也是面试时候经常问到的考点,下面结合源码总结一些hashmap的问题,本文以java8作为参考,也会讲与jdk1.7的部分区别:
1、hashmap的几个参数
默认初始化容量大小:DEFAULT_INITIAL_CAPACITY = 16;
最大桶容量:MAXIMUM_CAPACITY = 1073741824;
默认加载因子:DEFAULT_LOAD_FACTOR = 0.75f;
链表转红黑树的链表长度:TREEIFY_THRESHOLD = 8
链表转红黑树,桶最小容量:MIN_TREEIFY_CAPACITY = 64
红黑树转链表,红黑树的大小:UNTREEIFY_THRESHOLD = 6;
2、hashmap的数据结构是什么样子的
jdk1.7的数据结构为:数组+链表
jdk1.8及以后的数据结构为:数组+链表+红黑树
3、hashmap中hash值是如何获取的
static final int hash(Object paramObject)
{
int i;
return paramObject == null ? 0 : (i = paramObject.hashCode()) ^ i >>> 16;
}
将hashcode与它本身左移16位做了异或。目的是为了更好的散列
4、HashMap如何put进数据
putVal代码如下:
final V putVal(int paramInt, K paramK, V paramV, boolean paramBoolean1, boolean paramBoolean2)
{
Node[] arrayOfNode;
int i;
//若为空则先初始化
if (((arrayOfNode = this.table) == null) || ((i = arrayOfNode.length) == 0)) {
i = (arrayOfNode = resize()).length;
}
int j;
Object localObject1;
//此处寻址桶处无值,则进行数据插入
if ((localObject1 = arrayOfNode[(j = i - 1 & paramInt) ]) == null)
{
arrayOfNode[j] = newNode(paramInt, paramK, paramV, null);
}
else
{
//其他情况数据插入,分别分为treenode和链表两种情况
....
}
this.modCount += 1;
//如果达到扩容条件,则扩容,桶数量*扩容因子
if (++this.size > this.threshold) {
resize();
}
afterNodeInsertion(paramBoolean2);
return null;
}
方法里面寻桶算法如下:
if ((localObject1 = arrayOfNode[(j = i - 1 & paramInt)]) == null)
{
arrayOfNode[j] = newNode(paramInt, paramK, paramV, null);
}
如果是使用new HashMap();新建一个hashmap对象,初始容量为16,扩容因子0.75,当put进去的值满足如下条件:1、桶的大小大于64 2、插入的桶对应链表长度大于8,则将把链表转为红黑树。这种条件是比较苛刻的,源码里面有注释说明,在理想状态下,满足能将链表转为红黑树的概率:less than 1 in 10 millions
5、关于链表插入方面
HashMap在jdk1.8的时候,链表插入值从1.7的前插变成了尾插,原因是因为1.7使用前插的方式,在多线程高并发环境,可能造成死循环。当然,一般来说,碰到多线程的情况,使用concurrentHashMap更为合理
6、hashmap如何解决hash碰撞的问题
hash碰撞,对于桶数确定的数据结构,是一定会碰到的问题。首先,hashmap本身对于hashcode有重写,并且对寻桶算法上面,使用的也不是传统的取模运算,而是用的&运算,就是为了尽可能的散列,减少碰撞。一旦发生碰撞,一般的解决方案有两种:1、在碰撞的地方往后寻找位置进行放值,2、使用链表。hashmap使用链表的方式解决hash碰撞的问题,即一旦发生碰撞,则将值放入该桶对应的链表上面去