HashMap源码分析(一)

本文详细介绍了HashMap在JDK1.7到1.8的变化,包括其内部数据结构从"数组+链表"到"数组+链表+红黑树"的转换,以及扩容和树化阈值。还分析了HashMap的成员属性,如默认容量、负载因子和扩容策略。此外,解释了put方法的流程,包括hash计算、putVal方法和resize方法的工作机制,旨在理解HashMap如何处理冲突和提高查找效率。
摘要由CSDN通过智能技术生成

一、HashMap采用的数据结构:

HashMap在jdk1.7之间采用的是"数组+链表",但是在jdk1.8之后变成了"数组+链表+红黑树",而且链表由头插法变成了尾插法,当一个数组的链表长度达到TREEIFY_THRESHOLD阈值的时候会从链表转化成红黑树,当链表长度小于UNTREEIFY_THRESHOLD红黑树会退化成链表,每个元素的位置由路由寻址算法(table.length-1)&node.hash得出

二、HashMap成员属性分析:

类级常量元素:

//HashMap默认容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//HashMap的最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表转化成红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;
//红黑树退化成链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
//链表转化成红黑树的另一个条件:HashMap的最小容量为64
static final int MIN_TREEIFY_CAPACITY = 64;

对象级元素:

//哈希表在第一次put操作的时候初始化
transient Node<K,V>[] table;
//HashMasp长度
transient int size;
//被修改的次数,只有当HashMap添加删除时会改变,替换并不会改变
transient int modCount;
//扩容阈值:当哈希表中的元素超过这个值会发生扩容机制
int threshold;
//负载因子
final float loadFactor;

三、HashMap构造方法:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}
//本质上还是使用上面的构造方法    
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

当创建HashMap对象时调用两个参数的构造方法,当传入的初始化容量小于零时会出现异常,当传入的负载因子小于零时也会出现异常

当创建HashMap对象是调用一个参数或没有参数的时候,负载因子会默认为初始化0.75

扩容阈值调用了tableSizeFor()方法

tableSizeFor()方法:

作用:返回一个大于或等于cap的值,这个值一定是2^N次方

static final int tableSizeFor(int cap) {
    //为什么要减一,防止传入的参数刚好是2^N次方的时候使得容量扩容成原来的二倍
    int n = cap - 1;
    //n异或n无符号右移操作,使得低位全为1
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    //两个三元运算符,判断n是否小于零,是否大于最大容量,最后加一,使得容量变成2^N次方
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

四、HashMap的put方法:

1.put()方法:

public V put(K key, V value) {
        //调用hash方法和putVal方法
        return putVal(hash(key), key, value, false, true);
}

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;
        //当表容量为0或者为null时初始化hash表,第一次调用putVal方法的时候初始化散列表
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //当使用路由算法得到的索引的位置里为空时,直接新建一个Node节点存入
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //如果已经存在key,后续需要进行替换操作
            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);
            //链表的情况,且链表头元素的key与我们要插入的key不一致的情况
            else {
                for (int binCount = 0; ; ++binCount) {
                    //找到最后一个元素之后也没有找到一样的,直接新建一个Node节点存入,跳出循环
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果当前节点数量超过了树化阈值则进行红黑树的转变
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //找到了一致的key则后续需要进行替换操作,跳出循环
                    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增加
        ++modCount;
        //加完之后的长度大于了扩容阈值则触发扩容算法
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

3.hash()方法:

static final int hash(Object key) {
        int h;
        //让h无符号右移16位与h与运算
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

作用:为了在数组容量过低时,会出现频繁的hash冲突,让h无符号右移16位,使得高16位也参与运算,使分布更加散列,降低冲突

五、HashMap的resize()方法:

为什么要有扩容算法?当链表过长且容量过低的时候,hash冲突会随着数据的增加而增加,这时使用扩容算法使得hash更散列的分布,使用了空间换时间提高l查找效率

final Node<K,V>[] resize() {
        //扩容之前的table数组
        Node<K,V>[] oldTab = table;
        //初始化之前为0,初始化之后oldCap为扩容之前的table数组大小
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //扩容前的扩容阈值
        int oldThr = threshold;
        //扩容之后的容量,扩容之后的扩容阈值
        int newCap, newThr = 0;
        //如果已经初始化
        if (oldCap > 0) {
            //如果已经大于等于了最大容量把扩容阈值变成int类型的最大值
            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
        }
        //如果未初始化且扩容之前的扩容阈值大于0
        else if (oldThr > 0) // initial capacity was placed in threshold
            //则扩容为tableSizeFor()方法的返回值
            newCap = oldThr;
        //调用了new HashMap();的构造方法什么也没给
        else {               // zero initial threshold signifies using defaults
            //赋给默认容量 16
            newCap = DEFAULT_INITIAL_CAPACITY;
            //扩容阈值为默认负载因子*默认容量 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //当新的扩容阈值为0 的时候
        if (newThr == 0) {
            //扩容阈值为默认负载因子*新容量 
            float ft = (float)newCap * loadFactor;
            //判断新容量是否小于最大容量而且判断ft是否小于最大容量
            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;
        //如果不为null证明已经初始化过HashMap
        if (oldTab != null) {
            //循环数组
            for (int j = 0; j < oldCap; ++j) {
                //当前Node节点
                Node<K,V> e;
                //当前桶位有值
                if ((e = oldTab[j]) != null) {
                    //方便JVM回收内存
                    oldTab[j] = null;
                    //1.是单个元素的情况
                    if (e.next == null)
                        //算出在新的数组的位置
                        newTab[e.hash & (newCap - 1)] = e;
                    //2.是红黑树的情况
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //3.是链表的情况
                    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;
                            //hash....0 1111
                            //hash....1 1111
                            //判断是高位还是低位
                            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;
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值