深入解析HashMap源码(一)

使用key-value键值对映射的Map是非常经典的数据结构,作为Map的子类,HashMap是最常用的Map,根据键的hashcode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。由于HashMap源码在JDK1.7和JDK1.8版本的实现不同,这里讲的是1.8版本的HashMap。

PS:1.7和1.8版本的不同主要在于JDK1.7中使用一个Entry数组来存储数据,所有hashCode取模后相同结果的key都会被定位到Entry数组的同一个格子,key以链表的形式存储,当多个hashCode相同时,遍历需要经过一串很长的链表,需要遍历的操作(get/put等)遍历效率都会变低,时间复杂度在最差情况下会变为O(n)。而在1.8版本中,使用Node数组来存放数据,这个Node数组在一开始是链式结构,当达到某个条件时(key>8)会调用treeifyBin(),将链表转换为红黑树,由于使用了红黑树遍历查找元素,时间复杂度最差只有O(log n),这就是1.7和1.8版本的主要不同。

首先,看一下HashMap的类继承图

 继承抽象类AbstractMap的同时实现了Map, Cloneable, Serializable接口,实现了右侧所有的方法。

下图是HashMap核心的成员变量

  /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

DEFAULT_INITIAL_CAPACITY  是指HashMap容器的初始大小,MUST be a power of two意思是必须是2的幂次方,二进制格式中,1向左移动4位,也就是0001 --> 1 0000,即2^4=16。

为什么初始容量是16而不是4,8或其他呢?因为16的索引是0~15,在JDK中,都是用2进制的10进制数%16,结果在0~15之间。HashMap通过key的hashcode值,来进行位运算,公式:index = e.hash & (length - 1),在2的幂次方的情况下,length-1的所有二进制位全是1,hashCode码&(length - 1)的结果取决于hashCode本身,如果hashCode是均匀的,就可以减少hash碰撞情况(一个hash对应多个字符串),同时汇编处理的进制都是16进制,Java的底层是C,C的底层是汇编,越接近底层开发效率越快,所以初始容量是16位

MAXIMUM_CAPACITY 是指容量的极限值,默认设置为2^31

DEFAULT_LOAD_FACTOR 是指负载因子,默认为0.75f。负载因子的存在是非常重要的,由于初始化HashMap的容量大小是固定的,当存储的数据超过最大值*负载因子,即16*0.75=12时,将在当前容量的基础上进行扩容。JDK1.7版本是使用了rehash方法,创建新的链表,如果在新链表的数组索引位置相同,则链表元素会倒置。而JDK1.8版本中,链表元素相对位置没有变化, 实际是对对象的内存地址进行操作

TREEIFY_THRESHOLD 是指节点数,当一个元素被添加到至少有8(默认值)个节点的桶中时,桶中链表结构将转化为树形结构(红黑树)

UNTREEIFY_THRESHOLD 也是指节点数,不过是树形结构转换为链表结构

MIN_TREEIFY_CAPACITY  是指当桶被转化为树形结构的时候,此时桶所拥有的最小容量

以上是HashMap中重要的几个成员变量,接下来是核心的成员属性

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

    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;

table  这是一个Node数组,在第一次使用时需要初始化,并按需调整大小。当分配时,长度总是2的幂次方。(特殊情况下也允许为0)

entrySet  这是 键-值 对的集合,Set里面的类型是Map.Entry,这个接口里面设置了常用的取(设)键值方法,相当于一个JavaBean,对键值对进行了一个封装便于后面的操作

size  即map中所包含键值对的数量

modCount  用于记录集合的修改次数,包括对HashMap的映射修改以及结构修改,如果某个实现不希望提供快速失败迭代器,则可以忽略此字段

threshold  这个成员变量是阈值,是下一个容器的大小,决定了是否要将散列表再散列,它的值是capacity * load factor

loadFactor  负载因子

重点介绍table 属性,这是一个Node数组,数据存储的地方,它的结构是

数据传输进来的时候,位置的决定就是由公式  index = e.hash & (length - 1)  决定的,同时,next这个属性由null指向当前数据(Entry)的内存地址,例如0XFF110。当多个hashCode相同时,next指向也相同,这块内存地址就会出现一条单向链表,也就是一个接一个地往下挂,它的容量就是TREEIFY_THRESHOLD ,在JDK1.7的时候,可以一直往下挂到16个,而在JDK1.8里,只能挂到第七个(index0 --> index6),挂到第八个时就会进行红黑树的转换,存到桶的最大值64。同时,当节点减少到不足8个时,即UNTREEIFY_THRESHOLD <=6,红黑树就会转化为链表,这点在代码里的体现如下

            if (loHead != null) {
                if (lc <= UNTREEIFY_THRESHOLD)
                    tab[index] = loHead.untreeify(map);
                else {
                    tab[index] = loHead;
                    if (hiHead != null) // (else is already treeified)
                        loHead.treeify(tab);
                }
            }
            if (hiHead != null) {
                if (hc <= UNTREEIFY_THRESHOLD)
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }

以上就是核心的成员属性,下一章介绍HashMap核心的成员方法

深入解析HashMap源码(二)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方木丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值