java集合之Map篇——HashMap(底层源码非常详细说明)

前言

前面先做了红黑树的讲解平衡二叉树和红黑树-优快云博客,就是为了为了Map集合做铺垫,Map的几个实现集合底层都用到了红黑树。由于HashMap的东西有点多,HashTable和TreeMap下篇再说明。

一、HashMap

hashMap底层是哈希表+哈希桶(数组或红黑树)

5272764ff8f8442aa90ebf10bf622b94.png

 Set篇的几张图会漂亮一点

1.几个重要的内部类

(1)节点类型

a4e05517e41d4188bea87c615c18ce37.png

下面是两个节点内部类的源码:

链表节点类,最基本存储数据的节点类型。 

50726fc5d61f46ca934220c1a70b71da.png

 树节点类,树化后的哈希桶的节点的类型

822e6658cda949efabf9a529e27e956e.png

(2)利于遍历的内部类

    // 定义一个内部类EntrySet,继承自AbstractSet,泛型为Map.Entry<K,V>
    final class EntrySet extends AbstractSet<Map.Entry<K,V>> {

        // 返回EntrySet中元素的数量,即HashMap的大小
        public final int size()                 { return size; }

        // 清空EntrySet中的所有元素,实际调用HashMap的clear方法清空整个Map
        public final void clear()               { HashMap.this.clear(); }

        // 返回一个迭代器,用于遍历EntrySet中的所有元素
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }

        // 检查指定对象是否在EntrySet中存在
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))       // 如果传入的对象不是Map.Entry类型,直接返回false
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;   // 将传入的对象转换为Map.Entry类型
            Object key = e.getKey();             // 获取Map.Entry的key
            Node<K,V> candidate = getNode(hash(key), key); // 通过hash和key获取候选节点
            return candidate != null && candidate.equals(e); // 判断候选节点是否等于传入的Map.Entry对象
        }

        // 移除EntrySet中指定的对象,如果成功移除则返回true
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {          // 如果传入的对象是Map.Entry类型
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;   // 将传入的对象转换为Map.Entry类型
                Object key = e.getKey();           // 获取Map.Entry的key
                Object value = e.getValue();       // 获取Map.Entry的value
                return removeNode(hash(key), key, value, true, true) != null; // 通过hash、key和value尝试移除节点,如果成功返回true
            }
            return false;                          // 如果传入的对象不是Map.Entry类型,返回false
        }

        // 返回一个Spliterator,用于分割并行处理EntrySet中的元素
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }

        // 对EntrySet中的每个元素执行指定的操作
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            Node<K,V>[] tab;                      // 创建Node数组引用tab
            if (action == null)                   // 如果传入的操作action为null,抛出NullPointerException异常
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) { // 如果EntrySet的大小大于0且table不为空
                int mc = modCount;                // 记录当前的修改次数
                for (int i = 0; i < tab.length; ++i) { // 遍历table数组
                    for (Node<K,V> e = tab[i]; e != null; e = e.next) // 遍历table数组中的链表
                        action.accept(e);              // 对链表中的每个元素执行action操作
                }
                if (modCount != mc)               // 如果在执行过程中有其他线程修改了EntrySet
                    throw new ConcurrentModificationException(); // 抛出ConcurrentModificationException异常
            }
        }
    }

 EntrySet这个内部类的作用:方便遍历,取键值

A. k-v 最后是 HashMap$Node node =newNode(hash,key,value,null)

B. k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素的类型Entry,而一个Entry。

对象就有k,v    EntrySet<Entry<K,V>>即:transient Set<Map.Entry<K,V>> entrySet;

C.   entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node

这是因为 static class Node<K,V>implements Map.Entry<K,V>

D. 当把HashMap$Node对象存放到entrySet就方便我们的遍历,因为Map.Entry提供了重要方法

 K  getKey();   V   getValue();

2.几个重要的变量和常量

 哈希表即节点类型数组

cccaf14978e948d89d2921a4e8cbfdfd.png

默认初始容量16 

 d2e396b47b634827b8dda4b0eb894d7d.png

默认的加载因子0.75         

20fe193eb2f44094a7c840fa31893ea3.png

哈希表的最大容量 2^30

792a1b1d16024f1f982aa0fe5be73412.png

哈希桶树化的链表临界值8(达到这个值8还要哈希表达到最小树化容量64) 

495dffddaf524a9d8e312f7405d15ae7.png

树化的另外一个条件哈希表达到最小树化容量64

97952ae2b41847d8a9ba97de50b3df87.png

3.几个重要的方法

添加元素时分三种情况:

1.数组位置为null

2.数组位置不为null,键不重复,挂在下面形成链表或者红黑树

3.数组位置不为null,键重复,元素覆盖

(1)put()方法

//参数一:键
//参数二:值

//返回值:被覆盖元素的值,如果没有覆盖,返回null
   public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

(2)hash()方法

//利用键计算出对应的哈希值,再把哈希值进行一些额外的处理
//简单理解:返回值就是返回键的哈希值
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

(3)putVal()方法

//参数一:键的哈希值
//参数二:键
//参数三:值
//参数四:如果键重复了是否保留
//        true,表示老元素的值保留,不会覆盖
//        false,表示老元素的值不保留,会进行覆盖
//参数五:会传入一个空方法没有任何意义
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //定义一个局部变量,用来记录哈希表中数组的地址值。
        Node<K,V>[] tab; 

        //临时的第三方变量,用来记录键值对对象的地址值
        Node<K,V> p;

        //表示当前数组的长度    
        int n;

        //表示索引
        int i;

  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值