HashMap的底层由Entry[]数组构成:
此图引用自:https://blog.csdn.net/wanderlustLee/article/details/80747860
在JDK1.7中HashMap的基本属性包括:
- DEFAULT_INITIAL_CAPACITY = 16(Entry数组默认初始化长度)
- MAXIMUM_CAPACITY = 1 << 30 (数组可容纳最大长度)
- DEFAULT_LOAD_FACTOR = 0.75f (用于扩容的因子,按照默认长度,则扩容阈值:12)
通过反射研究HashMap中的put方法过程中这些值的变化:
首先介绍反射中的两个重要方法:
HashMap<Object,Object> map = new HashMap<>();
Class clazz = map.getClass();
//1.获取HashMap中所有属性
Field[] fields= clazz.getDeclaredFields();
foreach(Field field:fields){
field.setAccessible(true); //获取非Public属性的访问权限
}
//2.获取HashMap中所有方法
Method[] methods = clazz.getDeclaredMethods();
foreach(Method method:methods){
method.setAccessible(true); //获取非Public方法的访问权限
}
接下来分析HashMap的源码发现 put方法主要用到了两个内部方法:indexFor(int h,int length) 和 hash(Object k)
@Test
public void hashTest(){
HashMap<Object,Object> map = new HashMap<>();
for (int i = 0; i < 20; i++) {
map.put(i, "HelloWorld");
getAttribute(map,i);
}
}
public void getAttribute(Object o,Object k){
String nameVlues="";
Class<? extends Object> clazz = o.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
try {
Field field = fields[i];
field.setAccessible(true);
String name = field.getName();
Object value = field.get(o);
if("size".equals(name) || "threshold".equals(name))
nameVlues += field.getName()+":"+value+",";
else if("table".equals(name)){
Entry[] table = (Entry[]) value;
Object bucketIndex = getFuction(o,
"indexFor",
getFuction(o, "hash", k),table.length);
Entry entry=
table[Integer.valueOf(bucketIndex.toString())];
nameVlues += "table["+bucketIndex+"]:"+entry+",";
Class<? extends Entry> claz = entry.getClass();
Field next = claz.getDeclaredField("next");
next.setAccessible(true);
if (next.get(entry) != null)
nameVlues += "entry.next:"+next.get(entry)+",";
}
} catch (Exception e) {
e.printStackTrace();
}
}
//获取最后一个逗号的位置
int lastIndex = nameVlues.lastIndexOf(",");
//不要最后一个逗号","
String result= nameVlues.substring(0,lastIndex);
System.out.println(result);
}
public Object getFuction(Object o,String methodName,Object... k){
Class<? extends Object> clazz = o.getClass();
Object invoke = null;
try {
Method method = null;
if("indexFor".equals(methodName)){
method = clazz.getDeclaredMethod(methodName,int.class,int.class);
}else if("hash".equals(methodName)){
method = clazz.getDeclaredMethod(methodName,Object.class);
}
method.setAccessible(true); //解除访问限制
invoke = method.invoke(o, k);
} catch (Exception e) {
e.printStackTrace();
}
return invoke;
}
输出结果如下:
可以看到当put元素大于16个时,则Entry[]数组扩容两倍大小为 32(为啥是两倍不是三倍,默认的代码写死),因扩容因子为0.75,则有threshold:32*0.75 = 24 该值为下次扩容的一个临界值。
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
底层代码中扩容有两个条件:1.Entry[]数组大于阈值;2.Entry[]数组对应index中值不为null ;
可以增加循环添加数,可见table[index] index值为随机hash值;
至于hash碰撞,这个暂时无法测试概率较低。