HashMap详解

1. 简介

(1)HashMap是基于hash表实现的,以k-v键值对方式存储数据,k-v封装在Node结点中

(2)key不允许重复,但可以为null,value可以重复,也可以为null。当get()方法返回null时,可以表示表中没有该键值,也可以表示该键值在表中对应的数据为null,所有不能用get()方法判断表中是否存在该键,可以使用containsKey()方法来判断

(3)HashMap底层结构为数组+链表+红黑树,数组table里面存储的是Node类型数据,Node实现了Map.entry接口,Node结点里面包括hash、key、value和next,相同hash值的数据会加在Node后面形成链表,当链表达到一定规模之后会树化形成红黑树

(4)HashMap结构图

image
image

2. put()实现原理

(1)将k-v封装到Node结点中

(2)调用k的hashCode()方法得到对应的hashCode值,再将hashCode值右移16位与之前的hashCode值做异或处理,得到最终的hash值

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

(3)将hash值转化为数组下标:i = (n - 1) & hash

(4)通过数组下标寻找对应的数组位置,如果对应位置为空,则直接插入Node,如果对应位置不为空,则通过equals方法循环对链表中Node结点的k作比较,若返回true,则表示为同一个k,直接将v值替换,若返回false,则表示为不同的k,则将新的结点添加至链表末尾

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; //如果table表为空或者大小为0,则进行第一次扩容,大小为16
        if ((p = tab[i = (n - 1) & hash]) == null) //将hash值转化为数组下标
            tab[i] = newNode(hash, key, value, null); //如果数组下标对应table表中的位置为空,则创建一个新的Node用于保存k-v,并插入到该位置
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p; //如果hash相同,而且,准备加入的key与p所指Node的key值为同一个对象或者两者equals返回true,则不加入
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //判断p是不是一颗红黑树,如果是,则调用putTreeVal()方法进行添加
            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; //如果出现hash相同且equals返回true的情况,则直接break,退出循环
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value; //新的value值覆盖旧的value值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount; //加入的结点数+1
        if (++size > threshold)
            resize(); //扩容
        afterNodeInsertion(evict);
        return null;
    }

3. 扩容机制

(1)HashMap初始化table数组大小为16,装载因子为0.75,阈值为12

/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

(2)如果数组大小达到12,则会进行第一次扩容,扩容后大小为32

   if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; //新数组大小为旧数组大小的2倍(用<<1实现)
        }

(3)如果链表长度到达TREEIFY_THRESHOLD(默认为8),而且table数组长度到达MIN_TREEIFY_CAPACITY(默认为64),则会树化为红黑树,否则函数进行数组扩容

if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
posted @ 2022-04-02 13:58  shaun23  阅读(83)  评论(0)    收藏  举报