HashMap实现原理
HashMap的底层数据结构是数组+链表的形式。数组是HashMap的主体,链表则主要是为了解决哈希冲突而存在的,如果定位到数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,一步到位时间复杂度O(1)。如果有链表的话,对于添加,时间复杂度仍然为O(1)。但是查找操作,就需要遍历链表,然后通过key对象的equals 一一对比。
哈希冲突:如果两个不同的元素通过哈希计算处的实际存储地址相同,这就叫做哈希冲突,也叫哈希碰撞。
解决哈希冲突的方法:开放地址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,Hash采用的就是链地址法,即数组+链表的方式。(在jdk1.8中,如果链表长度大于7,则会转换为红黑树。小于7会将红黑树转换为原来的)
HashMap源码分析
HashMap是由一个Entry数组组成的,每一个Entry包含一个key-value键值对。
Entry代码
static class Entry<K,V> implements Map.Entry<K,V> {
//key - value 键值对
final K key;
V value;
//保存下一个节点
Entry<K,V> next;
//保存计算好的hash值,防止重复计算
int hash;
//构造方法
Entry(int h, K k, V v, Entry<K,V> n) { //e
value = v;
next = n;
key = k;
hash = h;
}
Entry是HashMap中的一个静态内部类,实现了Map.Entry<key,value>接口
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap继承了AbstractMap<K,V>类,实现了Map<K,V>,Cloneable和Serializable接口
HashMap的特点:
- 可以进行克隆。实现了Cloneable接口
- 可以进行序列化。实现了Serializable接口
- key可以为null,但只能有一个为null。
HashMap几个重要的字段
//数组默认初始化长度为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认的空数组
static final Entry<?,?>[] EMPTY_TABLE = {};
//
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//数组长度
transient int size;
//阙值,HashMap扩容时需要参考threshold
int threshold; //16*0.75 =12
//加载因子
final float loadFactor;
构造函数
/*
*HashMap有四个构造函数,
*不传参时将默认值当作参数,调用两个参数的构造函数,所以我们只用看最后的这个构造函数
*/
public HashMap(int initialCapacity, float loadFactor) {
//传入的初始容量不能小于0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//传入的初始容量不能大于最大容量MAXIMUM_CAPACITY
if (initialapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//增长因子不能小于等于0或者不是一个数
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//给增长因子和阙值赋值
this.loadFactor = loadFactor;
threshold = initialCapacity;
//调用父类的初始化函数进行初始化
init();
}
增加
public V put(K key, V value) {
//如果时空数组,进行数组填充
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key是null的,单独写了一个函数,来处理插入的是null的情况
if (key == null)
return putForNullKey(value);
//计算hash值,确保散列均匀
int hash = hash(key);
//计算在table中的实际位置
int i = indexFor(hash, table.length);
//遍历,如果对应数据已存在,用新value替换旧value,并返回旧value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//多线程,并发访问时,若HashMap内部结构发生变化,快速响应失败
modCount++;
//addEntry增加一个节点
addEntry(hash, key, value, i);
return null;
}
/*
*处理null的情况,放在table[0]的链表中
*如果在table[0]找到为null的key值,就更新,没有找到,头插法
*0号下标的链表存放的有null和其他的元素,但是null只能存在0号下标
*/
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
/**
* 扰动处理
*/
static int indexFor(int h, int length) {
//h&(length-1)相当于h % length,但是这样计算更快
return h & (length-1);
}
/*
*inflateTable为table数组分配存储空间
*/
private void inflateTable(int toSize) {
// capacity一定是2的次幂
int capacity = roundUpToPowerOf2(toSize);
//为阙值threshold赋值,取二者之间的最小值
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
/*
* roundUpToPowerOf2中的这段处理使得数组长度一定为2的次幂,
* Integer.highestOneBit是用来获取最左 边的bit(其他bit位为0)所代表的数值.
*/
private static int roundUpToPowerOf2(int number) {
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
/*
*
*
*/
final int hash(Object k) {
//hashSeed = 0
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
//hashCode()方法将对象k转换为一个整型
//native方法,将内存中的地址作为哈希码返回
h ^= k.hashCode();
//给h进行异或运算
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/*
*增加一个节点 1.7之前是头插法,1.8后加入红黑树,改成尾插法
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
//如果size比threoshold大&&当前下标不为null,进行扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建新的节点
createEntry(hash, key, value, bucketIndex);
}
/*
*创建新的节点
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
/*
*为HashMap扩容
*/
void resize(int newCapacity) {
//保存原来的table数组和长度
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//扩容的做大值不能大于MAXIMUM_CAPACITY
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//将数组扩大为原来的2倍
Entry[] newTable = new Entry[newCapacity];//32
//重新分配存储空间
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
//重新计算阙值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
//保存下一个节点
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//重新计算下标
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];//x
newTable[i] = e;
e = next;
}
}
}
读取get方法
public V get(Object key) {
//单独将null的情况封装了一个函数
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
//处理key=null的情况
private V getForNullKey() {
if (size == 0) {
return null;
}
//遍历table[0]的链表
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
/*
*获取节点
*/
final Entry<K,V> getEntry(Object key) {
//如果HashMap是空的,返回null
if (size == 0) {
return null;
}
//计算hash值
int hash = (key == null) ? 0 : hash(key);
//遍历并得到hash值相等且key不为空而且相等的位置,返回元素e
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}