源码解析 - HashMap 浅析

2 篇文章 0 订阅

本文是源码入门浅析,突破我们对源码的恐惧。
希望大家不要看到源码就畏惧,一点点来,我们一样可以一步步成为大神。

前面赠送一个简单的面试题给大家

HashMap和Hashtable的区别:

  1. HashMap 是从1.2开始的;Hashtable是从1.0开始的
  2. HashMap 是线程不安全的;Hashtable 是线程安全的
  3. HashMap 可以存 null 值 null 键;hashtable 不可以

HashMap1.7和1.8的问题:

  1. HashMap 1.7 在多线程下在 transfer 扩容是可能形成 循环链表,在get时会导致死循环。将cpu打满。
    同时可能存在数据丢失的问题。
  2. 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 的底层实现也有了一个比较好的认识。如果您对文章有什么建议,欢迎评论区留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值