详解详解Java HashMap实现原理实现原理
HashMap是基于哈希表的Map接口实现,提供了所有可选的映射操作,并允许使用null值和null建,不同步且不保证映射顺序。本文将记录一下研究
HashMap实现原理。
HashMap是基于哈希表的Map接口实现,提供了所有可选的映射操作,并允许使用null值和null建,不同步且不保证映射顺序。下面记录一下研究HashMap实现原理。
HashMap内部存储内部存储
在HashMap内部,通过维护一个 瞬时变量数组table (又称:桶) 来存储所有的键值对关系,桶 是个Entry对象数组,桶 的大小可以按需调整大小,长度必须是2的次幂。
如下代码:
/**
* 一个空的entry数组,桶 的默认值
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* 桶,按需调整大小,但必须是2的次幂
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
初始容量与负载因子初始容量与负载因子
HashMap有两个参数影响性能,初始容量和负载因子。容量是哈希表中 桶 的数量,初始容量只是哈希表在创建时的容量,负载因子是哈希表在其容量自动增加之前可
以达到多满的一种尺度。当哈希表中条目数超出了负载因子与当前容量的乘积时,则要对该Hash表进行rehash操作(即重建内部数据结构),重建时以当前容量的两倍
数目新建。可以通过构造器设置初始容量与负载因子,默认初始容量是16个条目,最大容量是2^30次方个条目,默认负载因子是0.75
桶 就像一个存水的水桶,它默认的初始存水容量是16个单位的水,默认在灌水灌到16*0.75时,在下次添加数据时会先扩充容量,扩充到32单位。0.75就是负载因子,
初始容量与负载因子可以通过创建水桶的时候进行设置。水桶最大的容量是2的30次方个单位的水。当初始容量设置的数量大于最大容量时,以最大容量为准。当扩展
时如果大于等于最大容量时则直接返回。
如下为如下为HashMap的部分源码,定义了默认初始容量、负载因子及其他一些常量:的部分源码,定义了默认初始容量、负载因子及其他一些常量:
/**
* 默认初始化容量,必须为2的次幂The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量,如果通过构造函数参数中传递初始化容量大于该最大容量了,也会使用该容量为初始化容量 * 必须是2的次幂且小于等于2的30次方 */
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的负载因子,可以通过构造函数指定
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 一个空的数组表,当 桶没有初始化的时候
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* 桶 , 存储所有的键值对条目,可以按需调整大小,长度大小必须为2的次幂
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
* Map中键值对的数量,在每次新增或删除的时候都会对size进行+1或者-1操作.
*/
transient int size;
/**
* 负载值,需要调整大小的临界值,为:(capacity * load factor).在每次调整大小后会使用新的容量计算一下
* @serial
*/
// If table == EMPTY_TABLE then this is the initial capacity at which the
// table will be created when inflated.
int threshold;
/**
* 负载因子,如果构造函数中没有指定,则采用默认的负载因子,
*
* @serial
*/
final float loadFactor;
/**
* HashMap结构修改次数,结构修改时改变HashMap中的映射数量或修改其内部结构(例如,* rehash方法,重建内部数据结构),此字段用于在 * HashMap的集合视图上生成的迭代器都处理成快速失败的
*/
transient int modCount;
初始容量与负载因子性能调整初始容量与负载因子性能调整
通常,默认负载因子(0.75)在时间和空间成本上寻求一种折中。负载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数HashMap类的操作中,包括
get和put操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其负载因子,以便最大限度的减少rehash操作次数。如果初始容量大于最大条
目数除以加载因子,则不会发生rehash操作。
如果很多映射关系要存储在HashMap实例中,则相对于按需执行自动的rehash操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效的存
储。
如下为重建HashMap数据结构的代码:
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) { // 如果容量已达最大限制,则设置下负载值后直接返回
threshold = Integer.MAX_VALUE;
return;
}
// 创建新的table存储数据
Entry[] newTable = new Entry[newCapacity];
// 将旧table中的数据转存到新table中去,这一步会花费比较多的时间
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
// 最后设置下下次调整大小的负载值
threshold = (int) Math.min(newCapacity * loadFactor,
MAXIMUM_CAPACITY + 1);
}