了解hashmap首先要从它的结构说起,hashmap的数据结构为数组+链表+红黑树,当链表的长度大于8且数组大小大于等于64时会将链表转化为红黑树。下面重点说一下put方法,从别的地方拿来了一张流程图,配合流程图更加容易理解。

java map批量put java map put方法_数组

下面解读一下put方法的源码:

public V put(K key, V value) {
	    //调用putVal方法,并计算key的hash值传入到putVal方法中
        return putVal(hash(key), key, value, false, true);
    }
/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // i为数组下标  p为数组项中存放的链表
        HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
        //判断table是否为空,table对象是node数组,即hashmap中数据+链表+红黑树中的数组
        if ((tab = table) == null || (n = tab.length) == 0)
            //对hashmap的临界值和数组进行初始化
            n = (tab = resize()).length;
        //计算当前对象存放的下标,并判断数组中该下标是否已经存值
        if ((p = tab[i = (n - 1) & hash]) == null)
            ///如果不存在则在该位置创建一个新的链表。并将目标值存入新链表的表头
            tab[i] = newNode(hash, key, value, null);
        else {
            HashMap.Node<K,V> e; K k;
            //判断目标key的hash值与链表头节点的hash值是否相等,它们的key是否相等
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //判断p节点的类型是否是TreeNode
            else if (p instanceof HashMap.TreeNode)
                //如果红黑树节点中不存在与目标key相同的值,将目标对象插入红黑树中,存在相同的key则返回已存在的对象
                e = ((HashMap.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);
                        //判断链表长度是否大于8
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //将链表转化为红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    //遍历的过程中发现链表中有key与目标对象的key相同
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //如果e不为空说明,hashmap中已经存在了当前目标对象的key,则将value进行覆盖
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                //修改值后,返回旧值
                return oldValue;
            }
        }
        //记录HashMap结构被修改的次数
        ++modCount;
        //判断数组大小是否已经大于临界值了
        if (++size > threshold)
            //进行扩容
            resize();
        afterNodeInsertion(evict);
        //Hashmap中不存在key,所以返回null
        return null;
    }
/**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     */
    final void treeifyBin(HashMap.Node<K,V>[] tab, int hash) {
        int n, index; HashMap.Node<K,V> e;
        //如果数组大小小于64
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            //进行扩容
            resize();
        //链表结构转红黑树
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            HashMap.TreeNode<K,V> hd = null, tl = null;
            do {
                HashMap.TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

查看完源码,再看一下demo,配置debugger跟踪查看hashMap的put方法效果更佳:

HashMap<HashValTest, String> stringHashMap = new HashMap<>();
        HashValTest test1 = new HashValTest("zcs1", 1);
        HashValTest test2 = new HashValTest("zcs2", 2);
        HashValTest test3 = new HashValTest("zcs3", 3);
        HashValTest test4 = new HashValTest("zcs4", 4);
        HashValTest test5 = new HashValTest("zcs5", 5);
        HashValTest test6 = new HashValTest("zcs6", 6);
        HashValTest test7 = new HashValTest("zcs7", 7);
        HashValTest test8 = new HashValTest("zcs8", 8);
        //链表长度大于8,调用treeifyBin()方法尝试将链表转化为红黑树,但是当前数组大小为16,没有达到64,所以将数组大小扩容为32
        HashValTest test9 = new HashValTest("zcs9", 9);
        //链表长度大于8,调用treeifyBin()方法尝试将链表转化为红黑树,但是当前数组大小为16,没有达到64,所以将数组大小扩容为64
        HashValTest test10 = new HashValTest("zcs10", 10);
        //链表长度大于8,调用treeifyBin()方法尝试将链表转化为红黑树,此时数组大小为64,以满足条件要求,成功将链表转化为红黑树。
        HashValTest test11 = new HashValTest("zcs11", 11);

        stringHashMap.put(test1, "1");
        stringHashMap.put(test2, "1");
        stringHashMap.put(test3, "1");
        stringHashMap.put(test4, "1");
        stringHashMap.put(test5, "1");
        stringHashMap.put(test6, "1");
        stringHashMap.put(test7, "1");
        stringHashMap.put(test8, "1");
        stringHashMap.put(test9, "1");
        stringHashMap.put(test10, "1");
        stringHashMap.put(test11, "1");
        System.out.println(stringHashMap);

对应的实体类:

/**
 * @author zhangchangsi
 * @version 1.0
 * @date 2022/3/17 23:30
 */
public class HashValTest {
    private String name;
    private int age;

    public HashValTest() {
    }

    public HashValTest(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        HashValTest that = (HashValTest) o;
        return age == that.age &&
                Objects.equals(name, that.name);
    }

    /**
     * 重点是重写hashCode方法,返回一个固定的值
     * @return
     */
    @Override
    public int hashCode() {
        return 2;
    }

    @Override
    public String toString() {
        return "HashValTest{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

下面再说一下hashmap解决hash冲突的方式:

      在数据结构中,处理hash冲常用的办法有:开发定址法、再哈希法、链地址法、建立公共溢出区。而HashMap处理hash冲突的方法就是链地址法。

     即当存在hash冲突时,以链表结构保存多个值,这种方法的基本思想就是将所有的哈希地址为i的元素构成一个称为同一次链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行,链地址法适用与经常进行插入和删除的情况。

java map批量put java map put方法_链表_02