HashMap源码(JDK1.8)

HashMap继承了AbstractMap<K,V>类,实现了Map<K,V>,Cloneable,Serializable接口,底层由数组+链表和红黑树(jdk8之前没有红黑树)来实现。源码需要关注的主要是构造函数,hash函数,put/get函数,resize函数,remove函数,clear函数等,以及内部字段。

  1. 基本字段

    transient Node<K,V>[] table;//这是HashMap底层的数组,所有的Hash结果都会作为这个数组的index
    
    transient Set<Map.Entry<K,V>> entrySet;//用于HashMap遍历的一个set
    
    transient int size;//当前HashMap的元素个数
    
    transient int modCount;//HashMap结构变化的次数,用于Fast-Fail机制,值得一提的是,jdk8中的modCount不再是volatile的,因为HashMap本就不是为多线程环境设计的,所以大量的用于单线程环境中,volatile关键字对写的效率影响很大,其次volatile不是线程安全的,就算加上也起不到特别大的作用
    
    int threshold;//HashMap resize的阈值,大于该值会触发HashMap扩容 threshold = capacity * loadFactor,需要注意HashMap内部不存在capacity这个字段,而是通过table.length读取
    
    float loadFactor;//HashMap的装载因子,根据Hash的原理,实际元素数量大于填充度大于装载因子之后可能会出现剧烈的Hash碰撞
    
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的HashMap初始容量是16,调用无参构造函数用到
    
    static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量
    
    static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认装载因子
    
    static final int TREEIFY_THRESHOLD = 8;//链表上的节点如果大于等于该值,会触发链表转化为红黑树,优化查询效率从O(n)到 O(logn),当负载因子为 0.75 时,根据泊松分布,同一个Hash出现8个节点的概率仅为0.00000006
    
    static final int UNTREEIFY_THRESHOLD = 6;//链表上节点小于等于该值,会退化为链表
    
    static final int MIN_TREEIFY_CAPACITY = 64;//在转化为红黑树的时候,会进行一次判断,如果当前table数组的长度小于这个值,则会再进行一次resize,而不是直接转化为红黑树
    
  2. 构造函数

    共四个构造函数

    public HashMap(int initialCapacity, float loadFactor);//指定容量和加载因子
    public HashMap(int initialCapacity)//仅指定容量
    public HashMap()
    public HashMap(Map<? extends K, ? extends V> m)//使用一个hashmap来构造
    

    主要看一下第一个带参构造函数

     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;
    
        /**
         * 需要注意的是,指定容量,并不代表就会创建这么大的table数组,
         * HashMap通过一个tableSizeFor函数将initialCapacity转化为大于等于他的最小2^n,
         * 然后赋值给threshold,在resize的时候,会创建threshold这么大的table数组,
         * 最后通过装载因子重新计算,为threshold赋值
         */
        this.threshold = tableSizeFor(initialCapacity);
    }
    
  3. hash函数

    static final int hash(Object key) {
            int h;
            //对key的HashCode的后16位和前16位进行异或,增强Hash分布的均匀性
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
    

    Hash函数的设计非常重要,需要保证Hash的均匀性,且计算不能太复杂

  4. resize函数

    final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            if (oldCap > 0) {//非初始化,因为HashMap已经有容量了
                if (oldCap >= MAXIMUM_CAPACITY) {//size已经达到了最大的扩容阈值
                    threshold = Integer.MAX_VALUE;//进一步调大扩容阈值,而不对size操作了
                    return oldTab;
                }
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // 这是Hashmap扩容中最核心的代码,双倍扩容
            }
            else if (oldThr > 0) // 带参构造函数的初始化,将HashMap的容量设置为threshold
                newCap = oldThr;
            else {               // 调用默认构造函数进行初始化
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            //然后重新更新threshold值,保证符合threshold = capacity * loadFactor
            threshold = newThr;
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            //由于Hashmap的2倍扩容,所以reHash一定会分成两个链表(红黑树)
            if (oldTab != null) { //扩容的ReHash核心代码,初始化不会进入此分支
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        if (e.next == null) //链表长度为1,直接rehash
                            newTab[e.hash & (newCap - 1)] = e;
                        else if (e instanceof TreeNode) //将红黑树拆分为两个,size小于6退化为链表
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        else { // 保持原序的链表拆分
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) { //和原来size与为0串成一个链表
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else { //和原来size与为1的串成另一个链表
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;//与为0的,放到原来的位置
                            }
                            if (hiTail != null) {//与为1的,放到原来的index+size的位置
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
    

    resize函数在初始化和put的时候都会调用,如果容量已经达到了最大就不在扩容,调整阈值为Int最大值;如果是在正常扩容情况下,会进行二倍扩容;如果是在有参构造函数以后调用,会创建大小等于threshold的table数组,然后更新threshold值;如果是无参构造函数之后调用,则一切使用默认值。扩容完毕之后进行数据的拷贝,也称为reHash,reHash没有重新计算Hash值,而是将hash值和原来的size进行位与操作,reHash会将链表/红黑树拆分为两个,然后分别连到原始的hash的index下,和原始hash的index+原size的index下。

  5. put函数

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

    put函数是一个空壳函数,核心代码都在putVal函数里

        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) //不存在Hash碰撞
                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)))) //因为有null判断,所以HashMap支持key为null,存在一个equal的key
                    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因为需要插入新的节点
                                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;
                    //LinkedHashMap继承HashMap,对LinkedHashMap起作用,HashMap中为空方法
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            //LinkedHashMap中用到
            afterNodeInsertion(evict);
            return null;
        }
    

    put函数是先对HashMap进行空判断,然后分为存在碰撞和不存在碰撞两种情况,存在碰撞又分为红 黑树插入和链表插入,链表插入后需要判断是否需要进行红黑树转化。最后需要进行扩容判断。

  6. get函数

    public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    

    同样get函数也是一个空壳函数,核心代码都在getNode函数中

        final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) { //首先map需要非空,hash的index存在值
                //((k = first.key) == key 这句代码说明了HashMap的key可以为空
                if (first.hash == hash && // 第一个节点
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                    if (first instanceof TreeNode) //查红黑树
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    do { //查链表
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    

    看完put函数,看get函数就很简单了

  7. remove函数

    public V remove(Object key) {
            Node<K,V> e;
            return (e = removeNode(hash(key), key, null, false, true)) == null ?
                null : e.value;
    }
    
    final Node<K,V> removeNode(int hash, Object key, Object value,
                                   boolean matchValue, boolean movable) {
            Node<K,V>[] tab; Node<K,V> p; int n, index;
            //第一个if在查找符合条件的节点
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
                Node<K,V> node = null, e; K k; V v;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    node = p;
                else if ((e = p.next) != null) {
                    if (p instanceof TreeNode)
                        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                    else {
                        do {
                            if (e.hash == hash &&
                                ((k = e.key) == key ||
                                 (key != null && key.equals(k)))) {
                                node = e;
                                break;
                            }
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                //删除节点
                if (node != null && (!matchValue || (v = node.value) == value ||
                                     (value != null && value.equals(v)))) {
                    if (node instanceof TreeNode)
                        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                    else if (node == p)
                        tab[index] = node.next;
                    else
                        p.next = node.next;
                    ++modCount;
                    --size;
                    afterNodeRemoval(node);
                    return node;
                }
            }
            return null;
        }
    
  8. foreach函数

    public void forEach(BiConsumer<? super K, ? super V> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.key, e.value); //函数式编程,编程者实现消费动作,用lambda表达式遍历
                }
                if (modCount != mc) //Fast-Fail机制
                    throw new ConcurrentModificationException();
            }
        }
    

    Example:

        public static void main(String[] args) {
            Map<String, String> map = new HashMap<>();
            map.put("aaa", "aaa value");
            map.put("bbb", "bbb value");
            map.forEach((a, b)->{
                System.out.println(a + " " + b);
            });
        }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值