本文是源码入门浅析,突破我们对源码的恐惧。
希望大家不要看到源码就畏惧,一点点来,我们一样可以一步步成为大神。
前面赠送一个简单的面试题给大家
HashMap和Hashtable的区别:
- HashMap 是从1.2开始的;Hashtable是从1.0开始的
- HashMap 是线程不安全的;Hashtable 是线程安全的
- HashMap 可以存 null 值 null 键;hashtable 不可以
HashMap1.7和1.8的问题:
- HashMap 1.7 在多线程下在 transfer 扩容是可能形成 循环链表,在get时会导致死循环。将cpu打满。
同时可能存在数据丢失的问题。- HashMap 1.8 在多线程下可能存在数据丢失的问题
其实HashMap 本来就不是在多线程环境下使用的,所以也不算是问题。
结论:HashMap 底层的实现是 数组 + 链表(单向链表)
HashSet 的底层使用的其实就是 HashMap,只不过,value为一个obj对象。
JDK说明:
如果两个对象equals后值为true,那么这两个对象的hash值也应该相同。
在执行put方法的时候,会先判断当前 桶索引位置 是否hash和equals都相同的key。如果有,说明key相同,执行覆盖value的操作。
如果没有,会遍历完该 桶索引位置的所有元素。
For循环结束后,会做一个 ++ 操作,以防止 并发修改异常。
并且会插入新的key-value键值对,新的元素会插入到头部(头插式)。
为什么使用头插式?
新的元素会在头部,可能考虑到的点是新插入的元素可能会被马上使用到,所以,放在头部,以减少查找的时间。
HashMap 底层的实现是 数组 + 链表(单向链表)
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
//这就是 HashMap 的底层容器。存入链表类型的数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//Entry链表对象,单链表:只有指向下一个节点的引用
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; //map的键
V value; //map的值
Entry<K,V> next; //指向下一个节点的引用
int hash; //hash值
}
}
put方法浅析
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);//put数据时,先检验如果为空,初始化一个数组大小。 table = new Entry[capacity];
}
if (key == null)
return putForNullKey(value); //如果key为空,做特殊处理,存入null键,只按传入的存储(具体逻辑同下)
int hash = hash(key); //计算当前key的hash值
int i = indexFor(hash, table.length); //计算出当前 key 所在的桶的索引 位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//for循环,遍历当前 桶索引 上的所有元素节点
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//如果元素的hash相等并且元素的key相等(两个key相等),则覆盖旧的值,并返回旧的值
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++; //防止并发修改
addEntry(hash, key, value, i);
}
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);
}
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++;
}
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
头插法图示:
看到这里,想必各位看官对 HashMap 的底层实现也有了一个比较好的认识。如果您对文章有什么建议,欢迎评论区留言。