关于Hashmap源码的学习总结

关于阅读HashMap源码的基本思路

Hashmap既然作为数据结构的存储容器,想必在其内部有一定实现来用于存储数据,因此我们首先要看的就是Hashmap中数据是以何种的形式存储,打开Hashmap源码发现有一个table变量,其数据类型为Node,hashmap是以key,value的形式存储数据,我们看下node方法发现,里面确实有存储key和value,而next是维护节点上下之间的关系,类似于指针的用法。

在这里插入图片描述在这里插入图片描述

Hashmap中常用的方法分析

如果要说我们使用hashmap最常用的方法就是put方法。这是我们经常使用的往容器中添加数据的基本方法。下面我会贴上hashmap 1.7版本以及1.8版本 然后一一对其做一些介绍。

代码实现步骤

    public V put(K key, V value) {
        // 当插入第一个元素的时候,需要先初始化数组大小
        if (table == EMPTY_TABLE) {
            // 数组初始化
            inflateTable(threshold);

        }
        // 如果 key 为 null,感兴趣的可以往里看,最终会将这个 entry 放到 table[0] 中
        if (key == null)

            return putForNullKey(value);
        // 1. 求 key 的 hash 值
        int hash = hash(key);
        // 2. 找到对应的数组下标
        int i = indexFor(hash, table.length);
        // 3. 遍历一下对应下标处的链表,看是否有重复的 key 已经存在,如果有,直接覆盖,put 方法返回旧值就结束了
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // key -> value

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }


        modCount++;
        // 4. 不存在重复的 key,将此 entry 添加到链表中
        addEntry(hash, key, value, i);

        return null;

    }

此段代码为hashmap中1.7的版本,作者已经对里面的一些基本内容做了一些注释,方便大家研究,从代码中可以看出,当新增数据的时候,我们首先会判断table是否为空,table刚才提过就是存储数据的node节点。如果数据为空的情况,我们就进行初始化,接下来我们点进去初始化的方法看一下。


 private void inflateTable(int toSize) {

        // Find a power of 2 >= toSize 保证数组大小一定是 2 的 n 次方。
        // new HashMap(519),大小是1024
        int capacity = roundUpToPowerOf2(toSize);

        // 计算扩容阈值:capacity * loadFactor
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        // 初始化数组
        table = new Entry[capacity];

        initHashSeedAsNeeded(capacity);

    }

这里是初始化为一个容量为16大小的数组,具体的方法自行研究,接下来我们往put方法继续往下看,我们看到最重要的一点,首先新增的key会经过hash计算,计算出其相对应的hash值,然后调用indexFor方法算出其在数组下标的位置,计算出下标的位置后就是进行数组的遍历,这里的数据是一种链表的形式存储,会先判断有没有存在重复的key如果有的话就将其值进行覆盖。其他便是插入新的数据。而在hashmap1.8中这里还多了一步操作当链表的长度大于7的时候,我们将链表转为红黑树的形式。具体大家可以看看以下代码

    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)
            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;
            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) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        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;
    }

掌握了1.7的方法 1.8的就不难研究,大家可以参考对比以下,至于数组的扩容,当size达到最大的容量的时候数据会进行扩容,代码如下,这里大概的意思是当扩容的时候我们会重新new 新的数组 扩容的大小为2的n次方。然后将旧的数据迁移到新的数组的地方,注意到这里,这里就会出现线程不安,当一个线程在进行扩容时候 又有另一个 线程进行添加数据操作会发生线程不安全。还有++size操作本身就不具备原子性的操作,因此出现了线程安全的容器ConcurrentHashMap,后面有机会我也会进行介绍,为什么这个容器是线程安全的。

 void resize(int newCapacity) {

        Entry[] oldTable = table;

        int oldCapacity = oldTable.length;
        // 如果之前的HashMap已经扩充到最大了,那么就将临界值threshold设置为最大的int值
        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);

    }

这些基本就是我对hashmap源码阅读解读,笔者是第一次讲解源码,以前无经验,如果有任何问题或者说错的地方欢迎下方评论,或者添加笔者微信讨论。通过最近的阅读源码总结了一套阅读容器的方法给大家分享。
VX:549896196

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值