HashMap 实现原理

简介

本文为我对 HashMap 实现原理的笔记整理以及一些个人理解,如若发现有错误的地方,欢迎留言指正

在不同的 Java 版本中 HashMap 的实现也略有不同,本文示例使用的 Java 版本为:“1.8.0_181”

什么是 Hash(散列函数)

Hash 音译为「哈希」,它是把任意长度的输入通过散列算法变换成固定长度的输出,这个输出称为散列值。

这种转换是一种压缩映射,也就是说散列值的空间远小于输入的空间,不同的输入可能会散列成相同的输出,所以 不可能从散列值来确定唯一的输入值

在这里插入图片描述

Map: key 和 value 的映射

在了解了什么是 Hash 后,我们再来看下什么是 Map

Map 是一种最基础的数据结构,它描述的是一个 key 和 一个 value 一 一对应的数据结构。

每种高级语言都有对 Map 的定义和实现。

Map 接口的定义

Map.java

public interface Map<K,V> {
   

···

    // 增加一个键值对
    V put(K key, V value);
    
    // 传入一个 key,并返回对应的 value
    // 注意此处传入的 key 的类型不同于 put 方法,不是泛型而是一个 Object
    V get(Object key);
    
    // 删除一个键值对
    V remove(Object key);

···

}
问题思考

为什么 Map 中 put 方法中传入的 key 的类型是泛型,而在 get 或 remove 方法中却是 Object 类型呢? 

HashMap的实现:数组+链表+(红黑树)

HashMap 是 Map 最常用的实现类,它是通过 key 的 hash 值来决定 value 在数组中的位置。但是上面我们也提到哈希值是有可能会重复的,也就是说不同的 key 得到的哈希值是一样的,这种情况我们称为 哈希冲突

哈希冲突 意味着不同的 value 要放在数组的同一个位置,那么 HashMap 是如何处理这种情况的呢?

在 Java1.7 中 HashMap 主要以数组+链表的形式实现的,而到了 Java1.8 则引入了黑红树算法来优化 HashMap 的查询速度。

当发生 哈希冲突 时,HashMap 会将相同的 value 以链表的形式存放到对应的数组位置中。

HashMap原理图

如上图所示,table 的初始长度为 16,每个节点都由四个元素组成:hash、key、value 和 next,其中 next 记录着下一个节点。

举个例子:我们根据 key1 的哈希值得出它在 table 的下标位置为 0,若当前 table[0] 还未有元素则创建一个 Node1,这个 Node1 记录着 key1 的哈希值、key1、key1 对应的 value 值以及 next(默认为 null),然后将这个 Node1 放入 table[0] 的位置中

之后我们再根据 key2 的哈希值得到它在 table 的下标位置也为 0,此时 table[0] 已经存在了一个 Node1, 此时 HashMap 同样会先创建一个 Node2,接着把这个 Node2 放到 Node1 的 next 中

HashMap 源码分析

AbstractMap.java

public abstract class AbstractMap<K,V> implements Map<K,V> {
   
    
...

}

HashMap.java

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

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cfNEbG8h-1611126122603)(135E65BA97E24261A01521DC8A56C92B)]
HashMap 继承了 AbstractMap 抽象类,而 AbstractMap 抽象类又实现了 Map 接口

阀值、负载系数、容量

在深入 HashMap 代码之前,我们先来认识 HashMap 的几个变量

HashMap.java

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

    // 初始容量 = 2的4次方 = 16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

    // 最大容量 = 2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // 默认负载系数
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    // 链表树形化的阈值
    static final int TREEIFY_THRESHOLD = 8;   
    
    ```
    
}

DEFAULT_INITIAL_CAPACITY 定义了 HashMap 的初始容量为 16

MAXIMUM_CAPACITY 则表示 HashMap 的最大容量为 230

比较有意思的是 DEFAULT_INITIAL_CAPACITY 和 MAXIMUM_CAPACITY 都是用位运算来定义的

HashMap.java

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
   
    
    ```
    
    // 默认负载系数
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    // 链表树形化的阈值
    static final int TREEIFY_THRESHOLD = 8;   
    
    ```
    
    // 扩容阀值
    int 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值