HashMap集合基本介绍和底层源码剖析

HashMap底层机制及源码剖析

HashMap类继承关系和构造器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gNNqCW4R-1647571748065)(E:\Typora笔记\java笔记\img\image-20220318100919761.png)]

构造器

Constructor and Description
HashMap() 构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。
HashMap(int initialCapacity) 构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。
HashMap(int initialCapacity, float loadFactor) 构造一个空的 HashMap具有指定的初始容量和负载因子。
HashMap(Map<? extends K,? extends V> m) 构造一个新的 HashMap与指定的相同的映射 Map

常用方法

Modifier and TypeMethod and Description
voidclear() 从这张地图中删除所有的映射。
Objectclone() 返回此 HashMap实例的浅拷贝:键和值本身不被克隆。
Vcompute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 尝试计算用于指定键和其当前映射的值的映射(或 null如果没有当前映射)。
VcomputeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的键尚未与值相关联(或映射到 null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非 null
VcomputeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 如果指定的密钥的值存在且非空,则尝试计算给定密钥及其当前映射值的新映射。
booleancontainsKey(Object key) 如果此映射包含指定键的映射,则返回 true
booleancontainsValue(Object value) 如果此地图将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>>entrySet() 返回此地图中包含的映射的Set视图。
voidforEach(BiConsumer<? super K,? super V> action) 对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。
Vget(Object key) 返回到指定键所映射的值,或 null如果此映射包含该键的映射。
VgetOrDefault(Object key, V defaultValue) 返回到指定键所映射的值,或 defaultValue如果此映射包含该键的映射。
booleanisEmpty() 如果此地图不包含键值映射,则返回 true
Set<K>keySet() 返回此地图中包含的键的Set视图。
Vmerge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。
Vput(K key, V value) 将指定的值与此映射中的指定键相关联。
voidputAll(Map<? extends K,? extends V> m) 将指定地图的所有映射复制到此地图。
VputIfAbsent(K key, V value) 如果指定的键尚未与某个值相关联(或映射到 null ),则将其与给定值相关联并返回 null ,否则返回当前值。
Vremove(Object key) 从该地图中删除指定键的映射(如果存在)。
booleanremove(Object key, Object value) 仅当指定的密钥当前映射到指定的值时删除该条目。
Vreplace(K key, V value) 只有当目标映射到某个值时,才能替换指定键的条目。
booleanreplace(K key, V oldValue, V newValue) 仅当当前映射到指定的值时,才能替换指定键的条目。
voidreplaceAll(BiFunction<? super K,? super V,? extends V> function) 将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。
intsize() 返回此地图中键值映射的数量。
Collection<V>values() 返回此地图中包含的值的Collection视图。

HashMap结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUZhF5nB-1647571748066)(E:\Typora笔记\java笔记\img\image-20220222101606511.png)]

源码分析

	 HashMap<Integer, String> maps = new HashMap<>();
        maps.put(1,"湛江");
        maps.put(2,"海康");
        maps.put(3,"西安");
        maps.put(4,"南宁");
        
        
        步骤1:使用无参构造创建一个 HashMap集合
        final float loadFactor;
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
        public HashMap() {
        	对加载因子 loadFactor 赋值为 0.75
        	this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    	}
    	
    	步骤2:调用 HashMap 中 put 方法
    	public V put(K key, V value) {
    		先调用 一个hash方法得到一个 hash 值
        	return putVal(hash(key), key, value, false, true);
    	}
    	
    	调用 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;
        如果表为空并长度为0时,就调用 resize 方法进行扩容 
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            如果根据 hash 和 表长度-1 计算结果得到一个索引位置为null时
        if ((p = tab[i = (n - 1) & hash]) == null)
        直接创建一个 newNode 对象 存放在该索引位置
            tab[i] = newNode(hash, key, value, null);
        else {
        如果存在进行如下判断 
            Node<K,V> e; K k;
            如果存在,并且两者key相等或equals值相同时,则不添加
            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) {
                如果在链表中没有该元素直接创建一个 newNode对象插入,跳出循环
                    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;
                    }
                    如果存在则不添加
                    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;
    }
    
    调用 newNode 方法进行创建一个 Node 对象
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }
    
    调用 HashMap 本类中 一个静态内部类 Node 进行相关初始化
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        
        
        扩容算法
        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) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            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 = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        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) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
        
      

记住结论:

扩容机制【和HashSet相同】到达临界值都是以2倍的方式进行扩容

  1. HashMap底层维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因为loadfactor初始化为0.75
  3. 当添加key-value时,通过key的哈希值得到在table的索引位置,然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的key是否相等,如果相等,则直接替换掉value值,如果不相等需要判断,是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容
  4. 第1次添加,则需要扩容table容量为16,临界值threshole为12(16*0.75)
  5. 以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,依次类推
  6. java8中如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化【红黑树】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g08U8zNe-1647571748066)(E:\Typora笔记\java笔记\img\image-20220222104752322.png)]

代码案例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KMT8Umsg-1647571748067)(E:\Typora笔记\java笔记\img\image-20220222115647306.png)]

package com.hspedu.map_;

import java.util.HashMap;

@SuppressWarnings({"all"})
public class HashMapSource1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java", 10);//ok
        map.put("php", 10);//ok
        map.put("java", 20);//替换value

        System.out.println("map=" + map);//

        /*老韩解读HashMap的源码+图解
        1. 执行构造器 new HashMap()
           初始化加载因子 loadfactor = 0.75
           HashMap$Node[] table = null
        2. 执行put 调用 hash方法,计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16)
            public V put(K key, V value) {//K = "java" value = 10
                return putVal(hash(key), key, value, false, true);
            }
         3. 执行 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;//辅助变量
                //如果底层的table 数组为null, 或者 length =0 , 就扩容到16
                if ((tab = table) == null || (n = tab.length) == 0)
                    n = (tab = resize()).length;
                //取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v
                //, 创建成一个 Node ,加入该位置即可
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
                else {
                    Node<K,V> e; K k;//辅助变量
                // 如果table的索引位置的key的hash相同和新的key的hash值相同,
                 // 并 满足(table现有的结点的key和准备添加的key是同一个对象  || equals返回真)
                 // 就认为不能加入新的k-v
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    else if (p instanceof TreeNode)//如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理
                        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);
                                //加入后,判断当前链表的个数,是否已经到8个,到8个,后
                                //就调用 treeifyBin 方法进行红黑树的转换
                                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value
                                ((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; //替换,key对应value
                        afterNodeAccess(e);
                        return oldValue;
                    }
                }
                ++modCount;//每增加一个Node ,就size++
                if (++size > threshold[12-24-48])//如size > 临界值,就扩容
                    resize();
                afterNodeInsertion(evict);
                return null;
            }

              5. 关于树化(转成红黑树)
              //如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
              //否则才会真正的树化 -> 剪枝
              final void treeifyBin(Node<K,V>[] tab, int hash) {
                int n, index; Node<K,V> e;
                if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                    resize();

            }
         */


    }
}
package collection_.collectionP.map_;

import java.util.HashMap;

/**
 * @author: 海康
 * @version: 1.0
 */
public class HasHMapSource01 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("java",10);
        hashMap.put("php",20);
        hashMap.put("java",20);
        System.out.println(hashMap);
    }
    /**
     * 第一步:执行构造器,完成加载因子初始化
     *     public HashMap() {
     *         this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
     *     }
     *
     *  第二步:执行put方法
     *    public V put(K key, V value) {
     *         return putVal(hash(key), key, value, false, true);
     *     }
     *   2.1:执行hash(key)方法获取hash值
     *    2.1.1
     *    static final int hash(Object key) {
     *         int h;
     *         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
     *     }
     *     2.1.2
     *     public int hashCode() {
     *       int h = hash;
     *       if (h == 0 && value.length > 0) {
     *           char val[] = value;
     *
     *           for (int i = 0; i < value.length; i++) {
     *               h = 31 * h + val[i];
     *           }
     *           hash = h;
     *         }
     *         return h;
     *     }
     *     2.2执行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)//如果table数组为null
     *             n = (tab = resize()).length;//则调用resize()方法完成扩容,第一次扩容为16
     *         if ((p = tab[i = (n - 1) & hash]) == null)//如果在table表中该索引位置为null
     *         //则直接将数据添加在该索引位置
     *             tab[i] = newNode(hash, key, value, null);
     *         else {//如果不为空则进行以下判断
     *             Node<K,V> e; K k;//铺助变量
     *             //如果p指定table表中的索引位置中的key值和准备添加到该索引位置的key值相同,
     *             //并两者指定相同对象引用或内容相同,则将p指定table表中索引位置数据赋给e
     *             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指定table表中索引的下一个位置为null,
     *                     //则直接添加到table表中的p指定table表中索引的下一个位置
     *                         p.next = newNode(hash, key, value, null);
     *                         //如果该链表大于8,则进行树化
     *                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
     *                             treeifyBin(tab, hash);
     *                         break;
     *                     }
     *                     //如果p指定table表中的索引位置中的key值和准备添加到该索引位置的key值相同,
     *                   //并两者指定相同对象引用或内容相同,则将p指定table表中索引位置数据赋给e
     *                     if (e.hash == hash &&
     *                         ((k = e.key) == key || (key != null && key.equals(k))))
     *                         break;
     *                     p = e;//重新将e赋给P,使用得P继续指定下一个位置
     *                 }
     *             }
     *             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;
     *     }
     */
}
5. 关于树化(转成红黑树)
//如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
}

注意是:不是一直扩容数组,当数组达到下面代码时,则不再扩容数组容量了

if (oldCap > 0) {
    if (oldCap >= MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return oldTab;
    }

模拟HashMap集合触发树机制代码

package collection_.collectionP.map_;

import java.util.HashMap;
import java.util.Objects;

/**
 * @author: 海康
 * @version: 1.0
 */
public class HashMapSource02 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        for (int i = 0; i < 12; i++) {
            hashMap.put(new A(i),"hello");
        }
    }
}
class A {
    public int num;

    public A(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "A{" +
                "num=" + num +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        A a = (A) o;
        return num == a.num;
    }

    @Override
    public int hashCode() {
//        return Objects.hash(num);
        return 168;//指定返回一个hashCode值
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值