Map集合之HashMap

1. HashMap 概述

HashMap 是通过 put(key,value) 存储,get(key)来获取。当传入 key 时,HashMap 会根据 keyhashCode() 方法计算出 hash 值,根据 hash 值将 value 保存在 bucket(桶)里。当计算出的 hash 值相同时,称之为 hash 冲突。HashMap 的做法是用链表和红黑树存储相同 hash 值的 value

2. jdk 1.8 之前与之后的 HashMap

  • jdk1.8 之前的 HashMap 是由 数组 + 链表 来实现的,数组是 HashMap 的主体。链表主要是为了解决 hash 冲突的
  • jdk1.8 之后的 HashMap 是由 数组 + 链表 + 红黑树 来实现的,在解决 hash 冲突时有了较大的变化。当链表长度大于阈值 8 时,并且数组的长度大于 64 时,此时此索引位置上的所有数据改为使用红黑树存储

在这里插入图片描述

3. HashMap 的数组,链表,红黑树之间的转换

  • 当创建 HashMap 集合对象的时候,在 jdk1.8 之前,是在它的构造方法中创建了一个默认长度是 16Entry[] table 的数组来存储键值对数据的。而从 jdk1.8开始,是在第一次调用 put 方法时创建了一个默认长度是 16Node[] table 的数组来存储键值对数据的
  • 数组创建完成后,当添加一个元素(key,value)时,首先计算元素 keyhash 值,以此确定插入数组中的位置。但是可能存在同一 hash 值的元素已经被放在数组同一位置了,这时就添加到同一 hash 值的元素的后面,他们在数组的同一位置,这就形成了单链表,同一各链表上的 Hash 值是相同的。当链表长度大于阈值 8 时,并且数组的长度大于 64 时,此时此索引位置上的所有数据改为使用红黑树存储,这样大大提高了查找的效率
  • 在转换为红黑树存储数据后,如果此时再次删除数据,当红黑树的节点数小于 6 时,那么此时的红黑树将转换为单链表结构来存储数据

4. HashMap 扩容机制

默认情况下,数组大小为 16,那么当 HashMap 中元素个数超过 16 * 0.75 = 12(这个值就是代码中的 threshold 值,也叫做临界值)的时候,就把数组的大小扩展为 2*16 = 32,即扩大一倍,然后重新计算每个元素在数组中的位置

0.75 这个值称为负载因子,那么为什么负载因子为 0.75? 这是通过大量实验统计得出来的,如果过小,比如 0.5,那么当存放的元素超过一半时就进行扩容,会造成资源的浪费;如果过大,比如 1,那么当元素满的时候才进行扩容,会使 get,put 操作的碰撞几率增加

5. HashMap 源码

5.1. HashMap 的基本属性

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
   
    // 序列号
    private static final long serialVersionUID = 362498820763181265L;    
    // 默认的初始容量是16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   
    // 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30; 
    // 默认的填充因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 当桶(bucket)上的结点数大于这个值时会转成红黑树;+对应的table的最小大小为64,即MIN_TREEIFY_CAPACITY ;这两个条件都满足,会链表会转红黑树
    static final int TREEIFY_THRESHOLD = 8; 
    // 当桶(bucket)上的结点数小于这个值时树转链表
    static final int UNTREEIFY_THRESHOLD = 6;
    // 桶中结构转化为红黑树对应的table的最小大小
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存储元素的数组,总是2的幂次倍
    transient Node<k,v>[] table; 
    // 存放具体元素的集
    transient Set<map.entry<k,v>> entrySet;
    // 存放元素的个数,注意这个不等于数组的长度。
    transient int size;
    // 每次扩容和更改map结构的计数器
    transient int modCount;   
    // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
    int threshold;
    // 填充因子
    final float loadFactor;
}

5.2. HashMap 中涉及到的数据结构

5.2.1. 链表节点(单链表)

NodeHashMap 的一个内部类,实现了 Map.Entry 接口,本质上是一个单链表的数据结构。链表中的每个节点就是一个 Node 对象

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是否相等,若key和value都相等,返回true。可以与自身比较为true
    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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值