HashMap底层深度解析

HashMap是Java中常用的Map实现,基于数组+链表或红黑树的数据结构。文章深入解析HashMap的构造方法、数据结构(包括数组和红黑树节点)、添加元素、resize()操作、取模运算以及hash()扰动函数。解释了为何负载因子默认为0.75,以及数组长度必须为2的幂次方。此外,还讨论了HashMap在面试中的常见问题,如初始化、扩容时数据位置的变化。
摘要由CSDN通过智能技术生成

简介

  • HashMap实现了Map接口,该接口主要有四个常用的实现类,分别是HashMap,LinkedHashMap,Hashtable,TreeMap,IdentityHashMap。

  • HashMap的数据结构:HashMap的数据结构为 数组+(链表或红黑树),上图:

    在这里插入图片描述

  • 为什么默认的负载因子是0.75?

    • 阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 根据HashMap的扩容机制,他会保证容量(capacity)的值永远都是2的幂 为了保证**负载因子x容量的结果是一个整数,**这个值是0.75(4/3)比较合理,因为这个数和任何2的次幂乘积结果都是整数。
    • 负载因子越大,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂,对于查询效率极其不利。负载因子越小,费的空间也就越大,底层的链表长度或者是红黑树的高度就会降低,查询效率就会增加。这是一个无法避免的利弊关系,当负载因子是0.75时,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。
    • https://www.cnblogs.com/kelelipeng/p/13261409.html#:~:text=%E5%A4%A7%E8%87%B4%E6%84%8F%E6%80%9D%E5%B0%B1%E6%98%AF%E8%AF%B4%E8%B4%9F,%E6%8F%90%E5%8D%87%E4%BA%86%E7%A9%BA%E9%97%B4%E6%95%88%E7%8E%87%E3%80%82

构造方法

//1、无参构造方法,默认大小是16,使用默认的加载因子0.75
public HashMap() {
   
        this.loadFactor = DEFAULT_LOAD_FACTOR; // 默认的加载因子0.75
}

//2、指定大小和加载因子的构造方法
public HashMap(int initialCapacity, float loadFactor) {
   
  //如果大小 > 2^31-1,那么设置容量为最大值
  if (initialCapacity < 0)
     throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
  //如果大小 > 2^31-1,那么设置容量为最大值
  if (initialCapacity > MAXIMUM_CAPACITY)
     initialCapacity = MAXIMUM_CAPACITY;
  //如果加载因子小于0或者不是数字的话,那么抛出异常
  if (loadFactor <= 0 || Float.isNaN(loadFactor))
     throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
	this.loadFactor = loadFactor;
  //threshold:要调整大小的下一个大小值(容量*加载因子)
  this.threshold = tableSizeFor(initialCapacity);
}

//3、指定大小,使用默认的加载因子0.75
public HashMap(int initialCapacity) {
   
  this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//Float的静态方法,传入的v不是一个数,那么就返回true
public static boolean isNaN(float v) {
   
   return (v != v);
}
//返回给定目标容量的 最小2的n次方数。
//2的n次方数:二进制高位为1,后面为n个0,例如2^1 = 0010、2^2 = 0100、2^3 = 1000。
//求得2的最小n次方数:将最高位1到最低位全置为1,最后将结果再+1,即可。
static final int tableSizeFor(int cap) {
   
  int n = cap - 1;
  //无符号右移运算符,将n和n>>>1进行或运算,最后再将值赋给n。
  n |= n >>> 1;//右移1位保证前两位为1.
  n |= n >>> 2;//再右移两位,保证前4位为1.
  n |= n >>> 4;//右移4位,保证前八位为1.
  n |= n >>> 8;
  n |= n >>> 16;//右移16位,保证前32位为1.
  return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  //如果n=8,那么最终返回的结果是16.
}

数据结构

数组

//该表,在首次使用时初始化,并根据需要调整大小。分配时,长度始终是 2 的幂。(我们还允许某些操作中的长度为零,以允许当前不需要的自举机制。
transient Node<K,V>[] table;

结点的数据结构

static class Node<K,V> implements Map.Entry<K,V> {
   
  final int hash;//key的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; }

  //结点Node的hash值
  public final int hashCode() {
   
    //两个哈希值做  异或运算(不带进位的二进制加法):0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0。
    //同为0,异为1.
    return Objects.hashCode(key) ^ Objects.hashCode(value);
  }

  //返回旧值
  public final V setValue(V newValue) {
   
    V oldValue = value;
    value = newVal
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值