HashMap容量
HashMap是啥~~~
以下是来自JDK15源代码中的定义:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
在集合框架中,把Map
接口实现并继承了AbstractMap
,所以是一个类
继续往下读这个类,第一个属性定义源码如下:
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
//最大容量为2^31;原因如下,size都只是定义为int型
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
默认初始化容量为16,而且必须初始化为2的n次幂,此处我试了一下,并不是指实例化时必须传一个2次幂整数作为参数。传入初始化容量后,它实际会调用一个修正函数:
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
个人解读:
n = -1 对应二进制1111 1111 1111 1111
numberOfLeadingZeros方法给定一个int类型数据,返回这个数据的二进制串中从最左边算起连续的“0”的总数量。
也就是找到遇到第一个1,左边(高位)有多少1,比如9的二进制1001,32位int表示的话,高28位都为0
>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
然后n得到的应该是一个大于等于n的数。
理由:
1.如果cap是2的次幂,如2^x次幂,那么2^x-1应该是x个1(最低位),那么实际n逻辑右移后得到的就是2^x-1
比如给16 : 0000 0000 0001 0000
减1得到: 0000 0000 0000 1111
调用numberOfLeadingZeros得到 : 28
完后:-1 >>> 28得到 0000 0000 0000 1111
2.如果不是2的次幂,那么得到的就是大于cap的最小的2次幂
return语句两个 ? : 运算符
第一个判断n和0的关系,如果n小于0,那么就返回1,否则还得比较一下有没有超过允许的1最大容量,不超过返回n+1
通过高效位运算就完成了容量修正。但是好像并不能直接通过方法返回容量。
查看HashMap
有参构造函数:
/* ---------------- Public operations -------------- */
/**
* Constructs an empty {@code HashMap} with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
threshold
字段存容量~~~,没方法直接获取这个指。
突然想起看过反射了,还没用过。试一试!!!
走起~
public class Main {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
HashMap<String,Integer> hm = new HashMap<String,Integer>(10);
//实例的getClass方法获取Class
Class<?> myhash = hm.getClass();
//获取指定属性,也可以调用getDeclaredFields()方法获取属性数组
Field threshold = myhash.getDeclaredField("threshold");
//将目标属性设置为可以访问
threshold.setAccessible(true);
System.out.println(threshold.get(hm));
}
}
传参初始化容量为10,不出意外应该输出16
有警告,说是JDK版本太高~~~,但却是输出了16。
put方法
index = HashCode(Key) & (Length - 1)
可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。
如果Length - 1不是全1的二进制,映射方法不平均,好像根据什么泊松分布得出的结论。
显然不符合Hash算法均匀分布的原则。