与hashmap深入交流

今天从源码角度进一步和hashmap 深入交流。

总所周知,hashmap底层是由数组实现的,更确切的说是由数组+单向链表实现的。而这个单向链表则由Node类中的next来实现。

transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
Node<K,V> next;
......
}

首先看一下hashmap的初始化,就看最具代表性的一个。我们需要提供初始容量和负载因子,这个负载因子用来扩容数组,如果数组容量到达了initialCapacity*loadFactor,就会引发数组扩容,这样可以减少不同元素存在同一个数组下标的几率,提高性能。

为什么loadFactor要定为0.75呢

如果太小,0.5的话,浪费了差不多一半的空间,人间不值得。如果太大的话,collision的几率就会变大,插入查找的时间复杂度就会从刚开始的O(1)降为O(n), 总的来说就是空间和时间的妥协。

这个threshold,并不是任意长度都可以,会根据我们给的initialCapacity去计算一个刚好大于这个数的2的幂。具体看源码的艺术:HashMap中的tableSizeFor方法

public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
}

现在我们主要来看一下hashmap是怎么存储数据的。
下面是难点解析

  1. tab[i = (n - 1) & hash,hash值为hash(key)得来的也就是数组下标,n-1就是数组长度减去1,因为数组长度为2的次方,所以n-1低位全是1,和hash &操作就相当于取模。
  2. 链表长度太长需要转化为红黑树,因为如果搜索一个无敌长的单链表是超级影响性能的,红黑树则会提高我们的效率。
    首先这个put方法,调用了putVal方法,可以看到这里有个hash方法。hashCode方法是Native方法,不管他。hash后的值为h,发现还有一步h^ (h >>> 16)操作。这是为什么呢?

这就是扰动函数

这一步是为了进一步的降低冲突几率。

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        ///如果数组为null,则先创建数组
        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;
            ///如果该数组已存的对象的hash值和key值一样,那么直接赋值,之后判断是否可以把value修改为最新值
            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;
    }

ok,现在对于hashmap就比较了解了。看完怎么存储值,再看怎么get值就更简单了,就不赘述了~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值