通过反射学习HashMap底层原理

HashMap的底层由Entry[]数组构成:

此图引用自:https://blog.csdn.net/wanderlustLee/article/details/80747860

在JDK1.7中HashMap的基本属性包括:

  1. DEFAULT_INITIAL_CAPACITY  = 16(Entry数组默认初始化长度)
  2. MAXIMUM_CAPACITY = 1 << 30  (数组可容纳最大长度)
  3. 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碰撞,这个暂时无法测试概率较低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值