0
点赞
收藏
分享

微信扫一扫

HashMap 源码

晴儿成长记 2022-03-17 阅读 46

阅读源码的时候应该关注HashMap的几个特征:

  1. 一些重要的静态成员变量
// HashMap的最大容量:2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
// HashMap的最小容量:16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 默认的负载因子,主要用于计算HashMap的threhold 
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当节点中至少有八个元素时,节点从链表转化为红黑树的阀值
static final int TREEIFY_THRESHOLD = 8;
// 当节点中数量为六时,红黑树蜕化为链表的阀值
static final int UNTREEIFY_THRESHOLD = 6;
// 红黑树的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
  1. HashMap中的一些静态方法
// 将Hash码的高位向右移与低位做异或,这样做的目的是为了保留高位特征,降低hash冲突
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
// 返回大于cap的最小二次方
static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
  1. 初始化HashMap的过程
// 手动指定HashMap的容量和负载因子
public HashMap(int initialCapacity, float loadFactor)
// 手动指定HashMap的容量,使用默认负载因子
public HashMap(int initialCapacity) 
// 容量和负载因子使用默认大小
public HashMap()
  1. 添加元素的过程
/**
     * Implements Map.put and related methods.
     *
     * @param hash  key的hash码
     * @param key 添加元素的key 
     * @param value 添加元素的value
     * @param onlyIfAbsent 如果为true,不改变已存在的valye
     * @param evict 如果为false,说明是刚创建向hashMap中添加元素
     * @return previous value, or null if none
     */
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)
        //HashMap中元素为空 或 HashMap长度为0
            n = (tab = resize()).length; // 初始化hashMap 
            
        if ((p = tab[i = (n - 1) & hash]) == null)
        //HashMap已经初始化完毕,此时tab[(n-1)&hash]] 元素为空
            tab[i] = newNode(hash, key, value, null);
            //创建新的节点 
        else { //HashMap 初始化完毕,节点位置已经存在元素(hash冲突)
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) //判断hash值 and key值与预期加入位置元素是否一致,如果一致做value替换
                e = p;
              
            else if (p instanceof TreeNode)
            // hash值 and key 与预期加入元素不一致,考虑链表是否已经转换为了红黑树,进入红黑树的节点插入逻辑
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);   
                
            else {
            /**尚未转换为红黑树,说明此时hashMap的(n-1)&hash位置元素长度小于8,做链表的遍历。一般两种情况会到这个逻辑:不同的key有相同的hashcode 相同的hashcode有不同的key,都说明当前指向的节点元素与要加入元素是不同的元素**/
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                    //如果p的next节点为空 将新的元素插入p的next节点中
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st   判断当前节点数量是否等于转换为红黑树的阀值
                            treeifyBin(tab, hash);
                            // 等于阀值 则转换为红黑树
                        break;
                    }
                    // 此时说明当前节点有next节点,需要把要加入的元素移动到链表尾部 ,判断e的hash与key 是否与待加入元素一致
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break; //相同 跳出循环
                    p = e; // 不相同,继续向后遍历
                }
            }
            if (e != null) { // existing mapping for key
            // 这里主要是做key 相同 值替换的逻辑
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                    // LinkedHashMap 要将节点后移
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount; // 计数器加1 size 加一 
        if (++size > threshold)
            resize();  // hashMap中元素大于临界值 要考虑扩容
        afterNodeInsertion(evict);
        return null;
    }

putVal()的几个作用:

  • 为刚初始化的HashMap做resize()
  • 保证HashMap中key的唯一性
  • 若发生hash碰撞:hash值不同 但映射到同一个位置;key不同,但计算出相同的hash
    这两种情况都说明加入的元素key是唯一的。但需要将新的元素加入对应桶的尾部,桶对应的链表长度大于等于8时,会出发treeifyBin的操作,将桶对应的链表转换为红黑树
  • 未发生hash碰撞,则说明元素唯一,可直接将key,value加入HashMap中,需要考虑扩容(size>threshold)问题
  1. 删除元素的过程
  2. 查找元素的过程
举报

相关推荐

HashMap源码

HashMap底层源码

HashMap源码详解

HashMap源码阅读

HashMap源码解析

HashMap源码分析

0 条评论