hashmap底层原理
(基于jdk7)
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 是无序的,即不会记录插入的顺序。
数组 + 链表结构实现
在调用put方式时,创建了16长度数组(Entry<K, V>,是一个单链表节点),首先会对key进行hash运算,计算出一个足够散列的hash之后与运算(求模)得到需要插入的数组位置;
进行判断,该数组位置是否为空:
如果为空:代表该位置没有值,直接插入
如果不为空:代表该位置已经存在值:
首选取出值,进行判断,如果要插入的值和某个节点相等(同一个对象)就进行值的替换。
那么就会创建链表结构,将节点插入到这个单向链表
注意:JDK7插入链表使用的头插法(新的节点是数组里面)
(七上八下)
数组容量到达3/4(0.75),数组就需要扩容,
每次扩容是原有容量的两倍!!!

//默认初始化容量为16,采用了位运算值是2的4次幂。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量为 2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子为0.75倍,就是当这个数组达到0.75倍的时候进行扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//空的
static final Entry<?,?>[] EMPTY_TABLE = {};
Entry(存储元素):
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; //记录每一个节点的key值
V value; //记录每一个节点的value值
Entry<K,V> next; //单向链表。记录下一个节点
int hash; //记录每一个节点的hash值
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
//键值对Entry的数量
transient int size;
//阈值(就是到达扩容的一个临界点)
int threshold;
//加载因子
final float loadFactor;
hashmap的构造函数:
public HashMap(int initialCapacity, float loadFactor) {
//如果初始化的容量小于0,抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果容量大于最大容量,就让容量等于最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//加载因子小于0或者不是数字,NaN:not a number(不是数字)
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
hashmap的put方法:
public V put(K key, V value) {
//如果这个table是空的,就初始化,在调用put方法的时候,创建一个默认为长度为16的数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key为空值,就放在坐标为0的链表里面
if (key == null)
return putForNullKey(value);
//hash的算法:大量的异或运算
int hash = hash(key);
//计算下标
int i = indexFor(hash, table.length);
//e等于下标为i的节点;e不为空;
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key值相等,就覆盖了;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//头插法
addEntry(hash, key, value, i);
return null;
}
inflateTable方法:(根据创建HashMap时传入的初始容量或者默认初始容量来创建数组,并初始化table。)
private void inflateTable(int toSize) {
// 判断是否是2的幂方
int capacity = roundUpToPowerOf2(toSize);
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
putForNullKey方法:
private V putForNullKey(V value) {
//e等于下标为0的节点;e不等于空,表示下标为0的节点不为空;如果key值不为空,就next
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
//HashMap中是允许key为null的,并将原来存在的value覆盖,返回原来的value
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
hash值算法:
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
//进行大量的异或运算使值足够的散列
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
indexFor(与运算):
static int indexFor(int h, int length) {
return h & (length-1);
}
addEntry:
void addEntry(int hash, K key, V value, int bucketIndex) {
//大于阈值并且要插入位置的链表不为空
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);
}
resize(扩容):
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
createEntry:
将新创建的Entry通过头插法插入到链表中
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++;
}
hashcode比较和equals比较:
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
}
两个对象的equals相等,那么他们的hashcode一定相等;hashcode相等,他们不一定相等;hashcode不相等,那么他们一定不相等;
这篇博客详细解析了HashMap在JDK7中的实现原理。HashMap基于数组和链表结构,使用头插法构建链表。当容量达到0.75倍时,会进行扩容至原先的两倍。插入元素时,首先计算key的hash值,然后根据hash值与数组长度取模确定插入位置。如果已有节点,则进行值的替换或创建新链表节点。此外,博客还介绍了hash算法、扩容策略以及Entry节点的结构。
987

被折叠的 条评论
为什么被折叠?



