前言
Hash:散列,将一个任意一个长度或者数据,通过特定的算法,转化成一个固定值
Map:地图,通过x,y的坐标去查找元素
模型图
描述
使用Entry
不足之处
使用场景:
单线程的key比较离散的环境中其查询,插入,删除的时间复杂度均为O(1)
哈希冲突
即不同的key其hash值可能相同,hashmap是通过链表的方法解决哈希冲突的
Resize在并发环境下可能造成循环链表
resize的过程,就是再散列调整table大小的过程,默认是当前table容量的两倍;在并发环境下会发生错误,导致数组链表中的链表形成循环链表,在后面的get操作时e = e.next操作无限循环,Infinite Loop出现。
单向链表造成时间复杂度上升O(n)
如果数据分布比较集中,可能导致某个槽位的链表过长。在get()时,其时间复杂度从O(1)变化为O(n),从而丧失了哈希表的意义。
非线程安全
HashMap允许可以在hash链的中间添加或删除元素,读操作将得到不一致的数据。多线程情况下的rehash可能会出现链路闭环等异常情况
源码分析
HashMap属性项:
int threshold; 阈值,用于设置扩容条件
threshold = capacity * load factor 当集合中元素个数size>threshold时需扩容
容量 Capacity(实质上并没有这个属性)
该属性并不存在,存在于put的局部变量中。用户设定的容量使用threshold进行接收,在put时,获取>=threshold的最接近的2>>n值,用于设定capacity。
初始默认容量为16:DEFAULT_INITIAL_CAPACITY = 1 << 4
其他属性
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认初始容量
static final int MAXIMUM_CAPACITY = 1 << 30; 最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认负载因子
static final Entry,?>[] EMPTY_TABLE = {}; 空的数组结构
transient Entry
transient int size; Map中元素的个数;size(),isEmpty()等方法的实现
final float loadFactor; 负载因子,默认值DEFAULT_LOAD_FACTOR = 0.75f
Entry
final K key; 当前节点的key
V value; 当前节点的value
Entry
int hash; 即为key的hash值
构造器
设置table={} ,用户设定的容量为threshold ,负载因子为loadFactor;
在put时发现table=={},则设定capacity,用于初始化table;同时设置threshold = capacity * load factor
核心方法
V get(Object key)
计算key的hash值
int hash = (key == null) ? 0 : hash(key);
获取数组中对应hash位置的链表
Entry
I = indexFor(int h, int length) {return h & (length-1);} //通过取模的方法计算对应的存储位置
便利链表获取对应的value
即key和链表元素e.key的hash相同,且(key和链表元素e.key == 或者 equals相等)
if (e.hash == hash && (e.key == key || (key != null && key.equals(e.key))))
V put(K key, V value)
如果数组为空,则初始化数组:if (table == EMPTY_TABLE)
使用用户设定的map容量(取大于等于该数的2>>n的值),初始化table 表
如果key已经存在,则直接替换value,参考get(key)的逻辑
如果 size >= threshold,则扩容,并返回新的bucketIndex
transfer(newTable),resize(2 * table.length):二倍扩容,建立新的Entry[],并将原来的Entry[]的所有元素按照对应key的hash,存在在新的Entry[]的对应链表中
将元素插入到数组中,即插入该元素到table并设置其为链表的头节点
Entry
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
Key可以为null的原因
if (key == null) return putForNullKey(value);
即产生了一个hash为0的Entry节点,存放在HashMap中