HashMap源码解析

总览

  1. JDK1.7和JDK1.8的区别
  2. 属性解释
  3. put()过程解析
  4. 计算threshold
  5. JDK1.8扩容优化
  6. JDK1.7死循环图解

区别

JDK1.7
  • 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。
  • 当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,极端情况HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。
  • 数组形成链表,新的节点添加在头节点(会有死循环)
JDK1.8
  • JDK7与JDK8中HashMap实现的最大区别就是对于冲突的处理方法。JDK 1.8 中引入了红黑树(查找时间复杂度为 O(logn)),用数组+链表+红黑树的结构来优化这个问题。
  • 数组形成链表,新的节点添加在尾节点
  • resize()不需要重新rehash()寻址
  • 解决了resize()时多线程死循环问题,但仍是非线程安全的
  • 数组形成链表,新的节点添加在尾节点

属性解释

属性定义
  • transient Node<K,V>[] table HashMap的哈希桶数组,非常重要的存储结构,用于存放表示键值对数据的Node元素
  • transient Set<Map.Entry<K,V>> entrySet HashMap将数据转换成set的另一种存储形式,这个变量主要用于迭代功能
  • transient int size HashMap中实际存在的Node数量,注意这个数量不等于table的长度,甚至可能大于它,因为在table的每个节点上是一个链表(或RBT)结构,可能不止有一个Node元素存在
  • transient int modCount HashMap的数据被修改的次数,这个变量用于迭代过程中的Fail-Fast机制,其存在的意义在于保证发生了线程安全问题时,能及时的发现(操作前备份的count和当前modCount不相等)并抛出异常终止操
  • final float loadFactor 也是加载因子,衡量HashMap满的程度,当实际大小超过临界值时,会进行扩容,默认0.75
  • int threshold 达到临界值,当元素达到临界值会进行扩容2倍,threshold = 加载因子*容量
默认属性
  • static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 默认初始大小16
  • static final float DEFAULT_LOAD_FACTOR = 0.75f 默认扩容是按照原容量的0.75倍进行扩容
  • static final int TREEIFY_THRESHOLD = 8 当某个桶节点大于8,且总数超过64,转化为红黑树,否则扩容
  • static final int UNTREEIFY_THRESHOLD = 6 当某个桶节点小于6时,会转化为链表,前提它是红黑树
  • static final int MIN_TREEIFY_CAPACITY = 64 当整个HashMap中的元素数量大于64时,且某个桶节点大于8,也会转化为红黑树结构
  • static final int MAXIMUM_CAPACITY = 1 << 30 最大容量

put()解析

public V put(K key, V value) {
   
    return putVal(hash(key), key, value, false, true);
}

发生冲突时,链表中新节点jdk1.7中是放在首位,jdk1.8是放在尾节点

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,  //这里onlyIfAbsent表示只有在该key对应原来的value为null的时候才插入,也就是说如果value之前存在了,就不会被新put的元素覆盖。
               boolean evict) {
   
    Node<K,V>[] tab; Node<K,V> p; int n, i;                     //定义变量tab是将要操作的Node数组引用,p表示tab上的某Node节点,n为tab的长度,i为tab的下标。
    // 将成员变量 table 赋值给本地变量 tab,并且将tab的长度赋值给本地变量 n
    // 如果tab为空或者 数组长度为0,进行初始化,调用 resize()方法,并且获取赋值后的数组长度
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
   
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMapJava中常用的数据结构,用于存储键值对,并支持O(1)时间复杂度的插入、查询、删除等操作。 HashMap源码解析如下: 1. HashMap是一个实现了Map接口的类,内部使用数组和链表实现。 2. HashMap中的键值对是以Entry对象的形式存储的,每个Entry对象包含一个键、一个值和指向下一个Entry对象的引用。 3. HashMap内部维护了一个默认容量为16的数组table,负载因子为0.75,默认扩容因子为2。当HashMap中的元素数量超过容量与负载因子的乘积时,即会触发扩容操作。 4. HashMap使用哈希函数将键映射到对应的数组下标上,实现快速查询。 5. 如果哈希函数产生了哈希冲突,即多个键映射到同一个数组下标上,HashMap会使用链表将这些键值对串起来,以便查询时遍历链表查找。 6. 在插入新的键值对时,HashMap会根据哈希函数计算出对应的数组下标,并将新的键值对插入到该位置的链表中。如果该位置的链表长度超过阈值(默认为8),则将这个链表转化为红黑树,以提高查询效率。 7. 在查询、删除键值对时,HashMap根据哈希函数计算出对应的数组下标,并遍历该位置的链表或红黑树,查找对应的键值对。如果链表或红黑树中没有对应的键值对,则返回null。 总之,HashMap是一个高效的数据结构,能够快速地插入、查询、删除键值对。不过,对于高度散列的数据集,也可能导致哈希冲突的增加,进而导致查询效率下降。因此,在使用HashMap时,需要合理地设置容量和负载因子,以及注意键的哈希函数的设计。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值