HashMap底层实现原理
HashMap在底层数据结构
采用了数组+链表+红黑树,(内部实现是一个桶数组,每个桶中存放着一个单链表的头结点,当链表长度大于8的时候转换为红黑树。)
通过散列映射来存储键值对数据因为在查询上使用散列码(通过键生成一个数字作为数组下标,这个数字就是hash code)
所以在查询上的访问速度比较快,HashMap最多允许一对键值对的Key为Null,允许多对键值对的value为Null。它是非线程安全的。在排序上面是无序的。
HashMap的主要成员变量:
transient Node<K,V>[] table:这是一个Node类型的数组(也有称作Hash桶),可以从下面源码中看到静态内部类Node在这边可以看做就是一个节点,多个Node节点构成链表,当链表长度大于8的时候转换为红黑树。
transient int size:表示当前HashMap包含的键值对数量
transient int modCount:表示当前HashMap修改次数
int threshold:表示当前HashMap能够承受的最多的键值对数量,一旦超过这个数量HashMap就会进行扩容
final float loadFactor:负载因子,用于扩容
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4:默认的table初始容量
static final float DEFAULT_LOAD_FACTOR = 0.75f:默认的负载因子
扩容机制核心方法Node<K,V>[] resize():
HashMap扩容可以分为三种情况:
第一种:使用默认构造方法初始化HashMap。从前文可以知道HashMap在一开始初始化的时候会返回一个空的table,并且thershold为0。因此第一次扩容的容量为默认值DEFAULT_INITIAL_CAPACITY也就是16。同时threshold = DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR = 12。
第二种:指定初始容量的构造方法初始化HashMap。那么从下面源码可以看到初始容量会等于threshold,接着threshold = 当前的容量(threshold) * DEFAULT_LOAD_FACTOR。
第三种:HashMap不是第一次扩容。如果HashMap已经扩容过的话,那么每次table的容量以及threshold量为原有的两倍。
这边也可以引申到一个问题就是HashMap是先插入数据再进行扩容的,但是如果是刚刚初始化容器的时候是先扩容再插入数据。
负载因子为什么会影响HashMap性能
我们都知道有序数组存储数据,对数据的索引效率都很高,但是插入和删除就会有性能瓶颈(回忆ArrayList),
链表存储数据,要一次比较元素来检索出数据,所以索引效率低,但是插入和删除效率高(回忆LinkedList),
两者取长补短就产生了哈希散列这种存储方式,也就是HashMap的存储逻辑.
负载因子表示一个散列表的空间的使用程度,有这样一个公式:initailCapacity*loadFactor=HashMap的容量。
所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。
反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成浪费,但是此时索引效率高。