HashMap底层原理(含面试题)

HashMap

以下是基于JDK1.8

1. HashMap结构图

image-20200918091218208

2. HashMap中定义的一些成员变量

// hashMap数组的初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 负载因子 0.75f;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 树形化阈值 8
static final int TREEIFY_THRESHOLD = 8;
// 解除树形化阈值 6
static final int UNTREEIFY_THRESHOLD = 6;
// 树形化的另一条件 Map数组的长度阈值 64
static final int MIN_TREEIFY_CAPACITY = 64
// 这个就是hashMap的内部数组了,而Node则是链表节点对象。
transient Node<K,V>[] table;
// 数组扩容阈值。
int threshold;
  • DEFAULT_INITIAL_CAPACITY:数组的初始容量为16。可以在构造方法中指定。必须是2的幂次方。(16->32->64…)
  • DEFAULT_LOAD_FACTOR:加载因子0.75f。所谓的加载因子就是HashMap的容量达到0.75时的时候会试试扩容resize(),(例:假设有一个HashMap的初始容量为16,那么扩容的阈值就是0.75*16=12。也就是说,在你打算存入第13个值的时候,HashMap会先执行扩容)。加载因子也能通过构造方法进行指定,如果指定大于1,则数组不会扩容,牺牲了性能不过提升了内存。
  • TREEIFY_THRESHOLD:树形化阈值。当链表的节点个数大于等于这个值时,会将链表转化为红黑树。
  • UNTREEIFY_THRESHOLD:解除树形化阈值。当链表的节点个数小于等于这个值时,会将红黑树转换成普通的链表。
  • MIN_TREEIFY_CAPACITY:树形化阈值的第二条件。当数组的长度小于这个值时,就算树形化阈达标,链表也不会转化为红黑树,而是优先扩容数组resize()
  • threshold:数组扩容阈值。即:HashMap数组总容量乘以加载因子。当前容量大于或等于该值时会执行扩容resize()。扩容的容量为当前HashMap总容量的两倍。比如,当前HashMap的总容量为16,那么扩容之后为32。

3. 继承关系

// table 内部数组是节点类型
static class Node<K,V> implements Map.Entry<K,V> {
   
    final int hash;
    final K key;
    V value;
    Node<K,V> next; // 下一个节点

    Node(int hash, K key, V value, Node<K,V> next) {
   
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        {
    return key; }
    public final V getValue()      {
    return value; }
    public final String toString() {
    return key + "=" + value; }

    public final int hashCode() {
   
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
   
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
   
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
   
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

拉链法的散列表是通过链表解决碰撞问题的,所以HashMap的内部数组是节点类型。

hash值是经过hash()方法处理过的hashCode,也就是数组的索引bucket,为了使hashCode分布更加随机。

java.util.HashMap<K, V>.Node<K, V>
java.util.LinkedMap<K, V>.Entry<K, V>
java.util.HashMap<K, V>.TreeNOde<K, V>

TreeNode是Node的子类,继承关系如下:Node是单向链表节点,Entry是双向链表节点,TreeNode是红黑树节点。TreeNode的代码400多行都是写的红黑树。

4. 简单总结

HashMap是基于拉链法实现的一个散列表,内部由数组和链表和红黑树实现。

  1. 数组的初始容量为16,而容量是以2的次方扩充的,一是为了提高性能使用足够大的数组,二是为了能使用位运算代替取模运算(据说提升了5~8倍)。
  2. 数组是否需要扩充通过负载因子来判断,如果当前元素个数为数组容量的0.75,就会扩充数组。这个0.75就是默认的负载因子,可由构造方法传入。也可以设置大于1的负载因子,这样数组就不会扩充,牺牲性能,节省内存。
  3. 为了解决碰撞,数组中的元素是单向链表类型。当链表长度到达一个阈值时(7或8),会将链表转换成红黑树提高性能。而当链表长度缩小到另一个阈值时(6),又会将红黑树转换回单向链表提高性能,这里是一个平衡点。
  4. 对于第三点补充说明,检查链表长度转换成红黑树之前,还会先检测当前数组是否到达一个阈值(64),如果没有到达这个容量,会放弃转换,先去扩充数组。所以上面也说了链表长度的阈值是7或8,因为会有一次放弃转换的操作。

5. 深入了解源码

5.1 构造方法

// 默认数组初始容量为16,负载因子为0.75
public HashMap() {
   
	this.loadFactor = DEFAULT_LOAD_FACTOR;
}
// 指定数组的初始容量
public HashMap(int initalCapacity) {
   
    this(initalCapacity, 
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

T Head

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

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

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

打赏作者

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

抵扣说明:

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

余额充值