前言
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
1、使用无参的构造函数创建HashMap
HashMap map = new HashMap<>();
System.out.println("map里元素的长度:" + map.size() );
System.out.println("map所占的空间大小:" + map.capacity() );
我们在这里创建一个HashMap对象,并打印2个信息:map里元素的长度、map所占的空间大小。(其中有一段是错误,稍后会讲到)
1.1、从空的构造函数入手分析
上述代码调用了HashMap的无参构造函数。
从注释可以看到, 构造了一个空的HashMap,使用默认的桶容量16 和 负载因子0.75(达到容量的0.75阈值,就触发扩容)
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
1.2、查看map里元素的长度
map.size()
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map
*/
public int size() {
return size;
}
这里的size使用的是Java基本类型int,初始值是0;
我们通过无参的构造函数,没有给size重新赋值,此时的size=0;
1.3、查看map所占的空间大小
map.capacity() 也就是桶容量,此代码【map所占的空间大小】在Idea里是报错的,编译不通过,但我们可以跟踪到JDK8的源代码,去自己计算此时桶容量大小是多少 :
capacity 方法不可重写,访问权限也是只能本类调用。
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
transient Node<K,V>[] table;
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
final int capacity() {
return (table != null) ? table.length :
(threshold > 0) ? threshold :
DEFAULT_INITIAL_CAPACITY;
}
上述的代码里,有2个三元运算:
第一个:如果table 不等于null,就取table的长度,否则就取第二个三元运算的结果;
第二个:如果threshold 大于0,就取threshold 的数据,否则取默认容量值。
1、threshold的注释
- DEFAULT_INITIAL_CAPACITY
默认初始容量 - Additionally, if the table array has not been allocated, this
field holds the initial array capacity, or zero signifying
此外,如果表数组尚未分配,则字段保持初始数组容量,或零表示 , - The next size value at which to resize (capacity * load factor)
要调整大小的下一个大小值(容量*负载系数)
也就是下一次扩容的阙值,元素个数达到这个阙值,整个容器就会扩容。
2、table
这里的table使用的是以K,V存储的Node节点集合,初始未赋值;
我们通过无参的构造函数,没有给table重新赋值,此时的table为null;
3、DEFAULT_INITIAL_CAPACITY
值=1 << 4; 采用二进制位运算,值为16
我们采用的是无参构造函数,因此经过2次三元计算比较,最终结果是DEFAULT_INITIAL_CAPACITY 。
1.4、使用反射查看map所占的空间大小
第1.3小节里,我们无法调用到HashMap的capacity方法,这里我们使用Java的反射技术来实现,调用capacity方法。
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
HashMap map = new HashMap<>();
//获取HashMap整个类
Class<?> hashMapClszz = map.getClass();
//获取指定属性,也可以调用getDeclaredFields()方法获取属性数组
Field threshold = hashMapClszz.getDeclaredField("threshold");
//将目标属性设置为可以访问
threshold.setAccessible(true);
//获取指定方法,因为HashMap没有容量这个属性,但是capacity方法会返回容量值
Method capacity = hashMapClszz.getDeclaredMethod("capacity");
//设置目标方法为可访问
capacity.setAccessible(true);
//打印刚初始化的HashMap的元素数量、阈值、容量
System.out.println("map里的元素数量:" + map.size() );
// System.out.println("map所占的空间大小:" + map.capacity() );
System.out.println("map触发扩容的阈值:" + threshold.get(map) );
System.out.println("map的容量:" + capacity.invoke(map) );
}
查看效果:
map里的元素数量:0
map触发扩容的阈值:0
map的容量:16
关键字
-
capacity:HashMap的容量,即哈希表中桶的数量。在创建HashMap时,可以指定初始容量,如果不指定,默认为16。当哈希表中的元素数量达到容量的75%时,会触发扩容操作。
-
threshold:HashMap的阈值,即哈希表中元素数量的上限。当哈希表中元素数量达到阈值时,会触发扩容操作。阈值的计算公式为:threshold = capacity * loadFactor。其中,loadFactor是负载因子,它的默认值为0.75。
-
size:HashMap中元素的数量。当向HashMap中添加元素时,size会自动增加;当从HashMap中删除元素时,size会自动减少。
-
modCount:HashMap的修改次数。当向HashMap中添加或删除元素时,modCount会自动增加。在迭代HashMap时,如果发现modCount发生变化,则会抛出ConcurrentModificationException异常,防止在迭代过程中修改HashMap导致数据不一致的问题。