HashMap原理

前言

Hash:散列,将一个任意一个长度或者数据,通过特定的算法,转化成一个固定值

Map:地图,通过x,y的坐标去查找元素

 

模型图

HashMap原理_第1张图片

描述

使用Entry[] table数组用于单向链表的头部节点(通过key的hash,使用求模的方法获取待插入对象在table的位置),又名为槽;而槽上的数据,通过单向链表的方式进行存储,并通过便利该链表以确保同一Map中key的唯一性,新插入的元素位于单向链表的头节点。

 

不足之处

使用场景:

单线程的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[] table = (Entry[]) EMPTY_TABLE; Entry链表的头节点

transient int size;   Map中元素的个数;size(),isEmpty()等方法的实现

final float loadFactor; 负载因子,默认值DEFAULT_LOAD_FACTOR = 0.75f

Entry结构

final K key; 当前节点的key

V value; 当前节点的value

Entry next; 下一个节点(链表存储结构)

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 e = table[indexFor(hash, table.length)]  //相当于链表的头节点

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 e = table[bucketIndex];  //bucketIndex为通过取模求出的在数组中的位置

table[bucketIndex] = new Entry<>(hash, key, value, e);

size++;

 

Key可以为null的原因

if (key == null) return putForNullKey(value);

即产生了一个hash为0的Entry节点,存放在HashMap中

你可能感兴趣的:(数据结构)