前言
在java1.8之后包含1.8HashMap做了很大的改善。不仅仅在底层数据结构上也包括一些方法
hashMap底层原理
hashMap在jdk版本不断迭代的前提下,HashMap底层实现也一直在改变
初始容量为16,如果在创建HashMap的时候没有指定容量,就使用初始容量。最大容量,HashMap中存储元素的数组的最大的容量,为2的30次方。
默认的加载因子为0.75F,在扩容的时候使用。当当前Hash中的容量大于容量*加载因子的时候边会发生扩容。扩大一倍也就是乘2
java1.6
public V put(K key, V value) {
// 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
if (key == null)
return putForNullKey(value);
// 根据key计算hashcode值。
int hash = hash(key.hashCode());
// 根据hashcode值查找在对应table中的下标。
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。直到e=null跳出,
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//这里对key进行判断,可能存在hashCode值相同但是key不同。key值存在,新值替换旧值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//当前HashMap实现了该方法但方法体为空,求大佬指教
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 当前下标处为null或者key值不存在,都会在该链表添加新的Entry到i索引处,当当前下标出存在Entry,单key值不同也就是索引相同,key值不同的时候也就形成了链表,解决了hash冲突。
addEntry(hash, key, value, i);
return null;
}
当key为null时,调用putForNullKey方法,将value放置在数组第一个位置,不为null根据key计算hashcode值。根据hashcode值查找在对应table中的下标。如果 i 索引处的 Entry 不为 null,通过循环不断遍历判断,这里对key进行判断,key值存在,新值替换旧。
当前下标处为null说明为新添加的
当不为null但是key值不存在,这也就说明出现了hash冲突。这时在该链表添加新的Entry到i索引处。形成一个多元素链表,
void addEntry(int hash, K key, V value, int bucketIndex) {
//把该位置上的链表赋值给一个变量
Entry<K,V> e = table[bucketIndex];
//把新添加的key、value创建程一个新的Entry,并指向这个临时变量。头插法
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//判断是否需要扩容
if (size++ >= threshold)
resize(2 * table.length);
}
//get源码
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
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.equals(k)))
return e.value;
}
return null;
}
java1.8
在JDK1.8中,HashMap的实现比之前的版本更加复杂,其中引入了红黑树的数据结构。也就是数组+链表+红黑树。
其中定义了变量TREEIFY_THRESHOLD=8,UNTREEIFY_THRESHOLD=6
当链表中的元素超过8个后则把该链表转化为红黑树,元素个数小于6,则把红黑树转换为链表。
在JDK1.6中,使用一个Entry数组来存放元素,而在JDK1.8中,使用的Node数组和TreeNode来存放元素。
node和Entry其实是一样的 ,TreeNode:表示的是一个红黑树结构
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
....省略
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...省略
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//首次初始化的时候
if ((tab = table) == null || (n = tab.length) == 0)
//对HashMap进行扩容
n = (tab = resize()).length;
//根据hash值来确认存放的位置。如果当前位置是空直接添加到table中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//如果当前位置非空,判断是否有hash冲突
else {
Node<K,V> e; K k;
//确认当前table中存放键值对的Key是否跟要传入的键值对key一致,即无冲突
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//存在冲突,确认是否为红黑树,如果是的话进行红黑树插入操作
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//存在冲突又不是红黑树
else {
for (int binCount = 0; ; ++binCount) {
如果hashCode一样的两个不同Key就会以链表的形式保存,采用尾插
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//判断链表长度是否大于8,treeifyBin会判断当前hashMap的长度,如果不足64,只进行resize,扩容table,如果达到64,那么将冲突的存储结构为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
//再次确认无冲突跳出
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//无冲突,但是当前位置存在值, 新的value替换旧的value并返回
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//当前HashMap修改次数
++modCount;
//如果当前HashMap的容量超过threshold则进行扩容
//size为当前HashMap包含的键值对数量
//表示当前HashMap能够承受的最多的键值对数量,size*加载因子,在扩容的时候进行赋值
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}