HashMap源码分析

377 阅读5分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

一个大腹便便的身穿格子衫的秃头老,缓缓的想你走来,心想看到这头秃的,就几根了,怎么也得顶级架构师吧!但是咱也是看过源码的人,不虚他!那么我们就开始今天的HashMap旅程吧!

小伙子,今天我就来考考你HashMap,看看你到底是骡子是🐎!

好嘞,那今天就看我的表现吧!(尼玛,你才是骡子,老子是🐎)!


秃头老:HashMap玩过吧,先简单介绍下!

我:HashMap主要实现了Map接口,主要用来存放键值对的,属于常用的Map集合。JDK1.7HashMap底层数据是数组链表,链表主要是用来解决冲突的,JDK1.8之后添加了红黑树,通过红黑树来提高查询效率。


秃头老:既然你提到了哈希冲突,那你分别说说1.7和1.8都是怎么解决的吧!(小崽子,抓细节,整垮你!)

我:(就是让你问这个的,掉我陷阱了吧,我就是勾引你那!)嗯...是这样的,我们先简单说一下添加流程,数组的每一个格子都是一个链表,当有一个数据添加进来的时候,先判断通过keyhashcode求出hash值,通过(n-1) & hash求出key的位置,如果不存在就放进去,如果存在在判断是否相同,相同的直接覆盖,不相同的话通过拉链法来解决,拉链法就是把冲突值加到链表中即可!

而1.8解决冲突的方式就是,如果链表的长度大于阈值(默认8)就是调用treeifyBin()方法,这个方法就是判断如果数组的长度大于64则就会转换成红黑树,来解决冲突,如果小于64则只进行扩容!


秃头老:既然你看过源码,那你说说HashMap有哪些重要的属性?(欸,这可是只有看源码才知道的哦 !)

我:(哎呦,竟然没问我put流程,可以可以!)首当其冲的是loadFactor加载因子,加载因子如果等于1则说明数组越紧凑,密度越小,链表也会越长,导致查询很慢;如果很小,则数组越稀疏,密度越大,导致空间的浪费,而默认的加载因子是0.75;第二个属性就是threshold,主要是判断什么时候进行resize()扩容,他的计算公式为threshold==loadFactor(0.75) * DEFAULT_INITIAL_CAPACITY(16);还有一些转换红黑树的阈值TREEIFY_THRESHOLD默认为8,最新树的容量MIN_TREEIFY_CAPACITY64等等。


秃头老:了解过put方法嘛,分别介绍一下1.7和1.8的put方法的流程把(你这么喜欢提1.7和1.8那就使劲问你!)

我:(哈哈哈哈,又掉进我的陷阱了,这次可以说是我全程牵着你的鼻子走)嗯...(假装摸摸头思考一下)那我先说1.7,首先通过hash方法确定key的位置,然后如果此位置没有元素则直接插入,如有元素,则判断key是否相同,相同则直接覆盖,不相同则通过头插法,插到链表头部。1.8的话是这样的。首先判断数组长度是否为0或者有没有初始话,如果没有先进行初始化,然后在通过hash求key的位置,跟1.7类似,但是是插入到链表的尾部,并且如果链表的长度大于阈值则调用treeifyBin()方法,这个方法就是判断如果数组的长度大于64则就会转换成红黑树,然后调用红黑树的put方法,最后判断如果实际大小大于阈值则进行扩容!

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 判断数组是否已经初始化,或者长度是否为0
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 当前位置没有值,则直接生成新的节点进行插入
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // key 和 插入的值相同的话,直接覆盖
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 如果是红黑树,则直接调用putTreeVal
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 插入链表尾部
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 如果链表的长度已经为7,则需要转换红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 实际大小大于阈值则进行扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

秃头老:嗯,那在说说HashMap的常用方法把!

我:(NTM,是不是没啥问的了,你是不是想开溜啊!)

/**
 * @author: tianjx
 * @date: 2021/12/21 20:40
 * @description: map 常用方法
 */
public class HashMapFunction {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("namekey","namevalue");
        map.put("passwordkey","passwordvalue");
        System.out.println(map);
        System.out.println("-------------------------------");

        Set<String> strings = map.keySet();
        for (String string : strings) {
            System.out.println(string);
            System.out.println(map.get(string));
        }
        System.out.println("-------------------------------");

        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }
        System.out.println("-------------------------------");

        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        for (Map.Entry<String, String> entry : entrySet) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
        System.out.println("-------------------------------");

        System.out.println(map.size());
        System.out.println(map.containsKey("namekey"));
        System.out.println(map.containsValue("namevalue"));
        System.out.println(map.replace("namekey","namevaluevalue"));
        System.out.println(map.get("namekey"));
    }
}

今天的大战秃头老就到这里了,控制一下量,偷个小懒,哈哈哈哈!喜欢的兄弟们持续关注一波,我是tianjx01,谢看官老爷的关注和点赞!

参考文章:Guide哥的Java学习

感谢大家的阅读,我是Alson_Code,一个喜欢把简单问题复杂化,把复杂问题简单化的程序猿! ❤