HashMap和ConcurrentHashMap底层源码详解以及相关面试题

文章目录

HashMap

几个知识点

  • 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。
  • 它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
  • 这个映射函数叫做散列函数,存放记录的数组叫做散列表。
  • JDK1.7:HashMap采取的是数组+链表的形式存储数据。
  • JDK1.8:引入了红黑树数据结构,利用红黑树快速增删改查的特点来优化了HashMap的性能。

为什么引入红黑树?

因为HashMap存在一个问题,即使负载因子和Hash算法设计的再合理,也无法避免出现在链表上拉链过长的问题,如果极端情况下出现严重的Hash冲突,会严重影响HashMap的存取性能。

构造器

构造一个具有指定初始容量和负载因子的空HashMap 。

public HashMap(int initialCapacity, float loadFactor) {
   
    //判断容量是否合法以及修正容量
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 判断负载因子是否合法,如果小于等于0或者isNaN,loadFactor!=loadFactor,则抛出异常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    //赋值loadFactor
    this.loadFactor = loadFactor;
    // 通过位运算将threshold设值为最接近initialCapacity的一个2的幂次方(这里非常重要)
    this.threshold = tableSizeFor(initialCapacity);
}

构造一个具有指定初始容量和默认加载因子 (0.75) 的空HashMap 。

public HashMap(int initialCapacity)

构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空HashMap 。

public HashMap()

构造一个与指定Map具有相同映射的新HashMap 。 HashMap是使用默认加载因子 (0.75) 和足以容纳指定Map中的映射的初始容量创建的。

public HashMap(Map<? extends K, ? extends V> m)

主干

该表在首次使用时初始化,并根据需要调整大小。分配时,长度始终是 2 的幂。 (我们还在某些操作中允许长度为零,以允许当前不需要的引导机制。)

transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
   
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    ...
}

字段

// 默认的初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量,1左移30位
static final int MAXIMUM_CAPACITY = 1 << 30;
// 构造函数中未指定时使用的默认扩容因子 0.75
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;
// 实际存储K-V键值对的个数
transient int size;
// 记录HashMap被改动的次数,由于HashMap非线程安全,modCount可用于FailFast机制
transient int modCount;
// 扩容阈值,默认16*0.75=12,当填充到13个元素时,扩容后将会变为32,
int threshold;
// 负载因子 loadFactor=capacity*threshold,HashMap扩容需要参考loadFactor的值
final float loadFactor;

put

将指定的值与此映射中的指定键相关联。如果映射先前包含键的映射,则替换旧值。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0tIKEds-1647496805497)(HashMap和ConcurrentHashMap.assets/image-20220317093936378.png)]

hash

static final int hash(Object key) {
   
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

(h = key.hashCode()) ^ (h >>> 16)

hash算法的步骤:

  • 第一步:通过key.hashCode()获取key的hashcode;
  • 第二步:通过(h = key.hashCode()) ^ (h >>> 16)进行高16位的位运算;
  • 第三步:通过(n - 1) & hash对计算的hash值取模运算,得到节点插入的数组所在位置。
image-20220317093817973

putVal

hash – 键的散列
key — 钥匙
value – 要放置的值
onlyIfAbsent – 如果为真,则不更改现有值
evict – 如果为 false,则表处于创建模式。

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)
        //判断键值对数组table[i]是否为空/null,是则执行resize()来创建
        n = (tab = resize()).length;
    // (n - 1) & hash计算插入节点在数组中的索引index,如果tab[i] == null则直接加入tab[i]
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 通过(n - 1) & hash对计算的hash值取模运算,得到节点插入的数组所在位置
        tab[i] = newNode(hash, key, value, null);
    // tab[i] != null的处理方法:
    else {
   
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 判断tab[i]的第一个元素(p)与插入元素key的hashcode、equals是否相等
            // 相等则覆盖
            e = p;
        else if (p instanceof TreeNode)
            // 判断tab[i]是否是红黑树节点TreeNode,是则在红黑树中插入节点
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
   
        // 则为链表
            for (int binCount = 0; ; ++binCount) {
   
                // 节点的下一个节点p.next为null,说明遍历到了链表的最后一个节点,将当前遍历到的最后一个节点的next指向新插入的节点e
                if ((e = p.next) == null) {
   
                    // 先插入链表
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //遍历tab[i]判断链表是否大于8,大于8则可能转成红黑树(前提是数组需要大于64,具体要看treeifyBin()中判断数组的长度是否大于64),满足则在红黑树中插入节点;否则在链表中插入;
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                	//在遍历链表的过程中如果存在key的hashcode、equals相等(说明该key存在)
                p = e;
            }
        }
        // 则替换value值即可。
        if (e != null) {
    // 现有键的映射
         
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Charte

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

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

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

打赏作者

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

抵扣说明:

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

余额充值