深度解析HashMap集合底层原理

本文深入探讨HashMap的底层原理,包括JDK1.8的实现细节,如五大关键点、构造方法、数据寻址、存储、删除及扩容机制。分析了HashMap为何使用位移操作、与运算和异或运算,并解释了为何HashMap长度必须是2的次幂。此外,还讨论了多线程环境下扩容可能导致的问题及其解决方案,以及HashMap与红黑树的关系,旨在帮助开发者理解HashMap的高效性和避免潜在问题。
摘要由CSDN通过智能技术生成

目录

  • 前置知识
    • ==和equals的区别
    • 为什么要重写equals和HashCode
    • 时间复杂度
    • (不带符号右移) >>>
    • ^异或运算
    • &(与运算)
    • 位移操作:1<<2=4,1左移两位为什么等于4
  • HashMap集合特点及源码分析(JDK1.8)
    • HashMap重要的五大点
      • 1.集合初始化
        • HashMap成员变量
        • HashMap内部数据结构
          • 链表
          • 红黑树
        • HashMap构造方法
          • 指定初始容量
          • 指定填充比
          • 用来初始化的Map
      • 2.数据寻址Get
      • 3.数据存储Put
      • 4.节点删除
      • 5.扩容原理
        • JDK1.7 HashMap中扩容机制resize()
          • 多线程环境下扩容造成死循环的分析过程
          • 解决办法
        • JDK1.8 HashMap中扩容机制resize()
  • HashMap面试题
    • HashMap什么时候会转换为红黑树
    • HashMap为什么要引进红黑树,为了解决什么问题?
    • HashMap的长度为什么必须是2的次幂?
    • 如何避免HashMap内存溢出问题
    • HashMap根据key查询的时间复杂度
    • HashMapKey为null存放在什么位置
    • HashMap底层是采用单链表还是双链表
    • HashMap底层是有序存放的吗
    • LinkedHashMap 和 TreeMap底层如何实现有序的
    • 为什么HashMap不使用取模运算
    • 求下标i=(n-1)&hash,为什么(n-1)变成了奇数
    • HashMap如何降低Hash冲突
    • 加载因子为什么是0.75而不是1
    • Hashap存放1W条数据怎么样效率最高
    • Hashmap1.7和Hashmap1.8的区别

前置知识#

==和equals的区别#

对于基本类型变量来说,只能使用 == ,因为基本类型的变量没有方法。使用==比较是值比较

对于引用类型的变量来说,==比较的两个引用对象的地址是否相等。所有类都是继承objcet类,而object类是equals方法比较的也是对象的地址是否相等,如果类没有重写equals方法,使用 == 和equals方法效果是一样的

为什么要重写equals和HashCode#

HashCode方法:底层采用C语言编写,根据对象地址转换为整数类型

如果两个对象的HashCode相等,对象的内容至不一定相等;hash碰撞的问题

如果使用equals方法比较两个对象内容值相等的情况下,那么hashcode的值也相等

因为equals默认情况下Object类采用==比较对象,那么比较的是内存地址是否相等,当数据类型只要不是基本类型,那么比较永远不会相等。

set集合存储的就是不重复的对象,底层就是hashmap,依据equals和hashcode进行判断

时间复杂度#

时间复杂度为O(n) 从头查询到尾部,查询多次

时间复杂度为O(1) 查询一次 比如根据数组下标查询

时间复杂度为O(logn) 平方查询 比如红黑树,

效率:O(1)>O(logn)>O(n)

(不带符号右移) >>>#

无符号右移就是右移之后,无论该数为正还是为负,右移之后左边都是补上0

无符号右移运算符和右移运算符的主要区别在于负数的计算,因为无符号右移是高位补0,移多少位补多少个0

15>>>2=0000 1111 右移两位=0000 0011=3

^异或运算#

相同为0,不同为1

2^3= 0010^0011=0001=1

&(与运算)#

00得0 11得1 01得0

2&3=0010&0011=0010=2

位移操作:1<<2=4,1左移两位为什么等于4#

这里的1是十进制,而计算机交流是用二进制,所以先要将1用二进制表示出来。

每一个符号(英文、数字或符号等)都会占用1Bytes的记录,每一个中文占2Byte

而一个1Bytes占8个bit,也就是8个二进制位

8位二进制数:28种不同状态 0000 0000 ~1111 1111=0~255=28=256

1的二进制表示0000 0001,然后进行位移操作。

位移操作向左边位移,后面空出来的补上0,越往左边越大,把0000 0001向左位移2位,变成了0000 0100,二进制0000 0100转化十进制所以为4,也可以说每左移一位是乘以2

8>>2?

0000 1000右移2位0000 0010,转化为10进制等于2

10>>2?

0000 1010右移2位0000 0010,转化为10进制等于2

1<<30?

0000 0001左移30位01000000 00000000 00000000 00000000,转化为10进制等于1073741824,也就是230

HashMap集合特点及源码分析(JDK1.8)#

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

HashMap继承了AbstractMap类,实现了Cloneable克隆接口、Serializable序列化接口、Map接口

特点:数组+链表+红黑树构成

深度解析HashMap集合底层原理

HashMap重要的五大点#

1.集合初始化#

HashMap成员变量#

//默认初始化hashmap容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //2的4次幂  16
//hashmap最大容量1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;//2的30次幂
//扩容因子 16*0.75=12 达到12就会进行扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表中存储元素的数量 > 8 时,会自动转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
//删除元素时,如果一个红黑树中中存储元素数量 < 6 后,会自动转换为链表
static final int UNTREEIFY_THRESHOLD = 6;
//数组容量>64&链表长度>8 转为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;

//阙值,用于判断是否扩容,threshold=容量*扩容因子=16*0.75=12
int threshold;
//扩容因子实际大小
final float loadFactor;
//HashMap中元素的数量 transient表示不能被序列化
transient int size;
//集合修改次数  防止多线程篡改数据
transient int modCount;
//存储元素的数组  单向链表
transient Node<K,V>[] table;

HashMap内部数据结构#

链表

//单向链表 实现了Entry接口  由上面的数组构成了数组加链表的结构
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;
    }
   //比较两个Node是否相等
    public final boolean equals(Object o) {
  
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值