java8 HashMap接口实现源码解析

本文详细解析了 Java8 中 HashMap 的实现,包括类属性、静态方法、构造器以及 Map 接口的实现。重点讨论了 HashMap 的位运算优化、负载因子、构造器方法、put、get、resize、remove、红黑树转换以及迭代器实现。还介绍了 Java8 中引入的 Spliterator 接口和 HashMap 的序列化处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、类属性

二、静态方法

三、类构造器方法

四、Map接口实现


一、类属性

    /**
     * 默认的初始容量是16,必须是2的倍数
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认负载因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 桶内节点数大于8个时,存储结构由单向链表转换成红黑树
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 扩容时当桶内节点数小于6个时红黑树转换成单向链表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     *桶存储结构由列表转换成树时数组的最低容量,低于该容量时通过扩容数组解决部分桶节点过多问题
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /* ---------------- Fields -------------- */

    /**
     *存储Node的数组,总是2的整数次幂
     */
    transient java.util.HashMap.Node<K,V>[] table;

    /**
     *保存元素的Set集合
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * 包含的键值对个数
     */
    transient int size;

    /**
     *记录修改次数,实现在遍历map时如果修改map会快速失败的功能
     */
    transient int modCount;

    /**
     * 进行扩容的临界值,根据初始容量计算出来的实际最大容量再乘以负载因子
     */
    int threshold;

    /**
     * 负载因子
     */
    final float loadFactor;

二、静态方法

    /**
     * 该方法用于计算key的hash值,然后用该hash值对HashMap的容量取模,算出key存储的位置
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

为什么要做上述的位运算了?首先实际生产中HashMap的容量基本不会超过2^16,即65536个,这种大Map通常都放到缓存组件中了,所以用key的hash值的低16位是最关键的。其次,hashCode()方法返回的是一个int类型,最长32位,右移16位且高位补0(h>>>16)后与原值做异或(两者相同为0,不同为1),相当于高16位和低16位都参与了运算,与原来的只有低16位参与运算相比可以高效的避免hash值不同最后取模结果一样的情形,使key的分散更均匀,提高查找效率。参考如下例子:

keyA hashCode  0000 0000 0000 1100 1010 1111 0000 1000 

keyB hashCode  0000 0000 0000 0011 1010 1111 0000 1000 

HashMap容量n    0000 0000 0000 0000 0010 0000 0000 0000
          n-1   0000 0000 0000 0000 0001 1111 1111 1111

只有低16位参与计算时,hashCode对容量取模的结果都是:
               0000 0000 0000 0000  0000 1111 0000 1000

当高16位参与运算时:
keyA h>>>16     0000 0000 0000 0000 0000 0000 0000 1100
     ^运算       0000 0000 0000 1100 1010 1111 0000 1100
     取模        0000 0000 0000 0000 0000 1111 0000 1100

keyB h>>>16     0000 0000 0000 0000 0000 0000 0000 0011
      ^运算      0000 0000 0000 0011 1010 1111 0000 1011
      取模        0000 0000 0000 0000 0000 1111 0000 1011

最终取模的结果不一样
   /**
     * 该方法用于计算HashMap的实际容量,cap为用户输入的初始容量,实际容量必须是最近的大于或者等于初始容量的2的整数次幂,比如10,则返回16
    */
  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;
        //n最大可能为2^31-1,所以先判断是否大于2^30,如果不是才能加1,否则会超过int类型的最大值
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

参考如下计算过程:

初始值
00000000 11000000 00000000 00010011
n-1
00000000 11000000 00000000 00010010
>>>1
00000000 01100000 00000000 00001001
|=
00000000 11100000 00000000 00011011
>>>2
00000000 00111000 00000000 00000110
|=
00000000 11111000 00000000 00011111
>>>4
00000000 00001111 10000000 00000001
|=
00000000 11111111 10000000 00011111
>>>8
00000000 00000000 11111111 10000000
|=
00000000 11111111 11111111 10011111
>>>16
00000000 00000000 00000000 11111111
|=
00000000 11111111 11111111 11111111
n+1
00000001 00000000 00000000 00000000

从上述计算过程可以发现,其实初始值的低位是没有用到的,关键是初始值的最高位的1,每次右移再做或运算相当于不断的在最高一位1的后面不断填充1,第一次右移1位,填充1个1,第二次右移2位,填充2个1,第三次右移4位,填充4个1,第四次右移8位,填充8个1,最后一次右移16位,填充16个1,如果最高位的1的后面都是1,则后面的位运算无意义。最后通过加1,算出结果。为什么不会最多会右移16位了?因为int是32位,右移16位最多填充31个1,当最高位的1是第一位时,也能让后面都填充为1。 

    /**
     * 如果x实现了Comparable接口则返回x的Class,否则返回null
     */
    static Class<?> comparableClassFor(Object x) {
        if (x instanceof Comparable) {
            Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
            if ((c = x.getClass()) == String.class) // bypass checks
                return c;
            //获取该类实现的接口
            if ((ts = c.getGenericInterfaces()) != null) {
                for (int i = 0; i < ts.length; ++i) {
                    //如果该类实现了Comparable接口,并且接口的参数化类型就是该类本身
                    if (((t = ts[i]) instanceof ParameterizedType) &&
                        ((p = (ParameterizedType)t).getRawType() ==
                         Comparable.class) &&
                        (as = p.getActualTypeArguments()) != null &&
                        as.length == 1 && as[0] == c) // type arg is c
                        return c;
                }
            }
        }
        return null;
    }

    /**
     *k和x进行比较,要求k实现Comparable接口,x的Class类型就是kc
     */
    @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
    static int compareComparables(Class<?> kc, Object k, Object x) {
        return (x == null || x.getClass() != kc ? 0 :
                ((Comparable)k).compareTo(x));
    }
ParameterizedType接口参考:https://www.cnblogs.com/linghu-java/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值