Map接口之HashMap

本文详细介绍了Map接口中的HashMap实现类,解析了HashMap的键值对概念、存储结构及其在JDK 7和8中的底层实现原理。讨论了负载因子对性能的影响,并简单提及了HashMap的源码分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Map接口

map接口的介绍和实现类

  1. map接口的继承树
    在这里插入图片描述

  2. Map接口概述

    • Map与Collection并列存在。用于保存具有映射关系的数据:key-value
    • Map 中的 key 和 value 都可以是任何引用类型的数据
    • Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
    • key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
    • Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类
  3. 常用方法

    • 添加、删除、修改操作:

      Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
      void putAll(Map m):将m中的所有key-value对存放到当前map中 
      Object remove(Object key):移除指定key的key-value对,并返回value
      void clear():清空当前map中的所有数据
      
    • 元素查询的操作

      Object get(Object key):获取指定key对应的value
      boolean containsKey(Object key):是否包含指定的key
      boolean containsValue(Object value):是否包含指定的value
      int size():返回map中key-value对的个数
      boolean isEmpty():判断当前map是否为空
      boolean equals(Object obj):判断当前map和参数对象obj是否相等
      
    • 元视图操作的方法:

      Set keySet():返回所有key构成的Set集合
      Collection values():返回所有value构成的Collection集合
      Set entrySet():返回所有key-value对构成的Set集合
      

Map实现类之一:HashMap

1. HashMap是 Map 接口**使用频率最高**的实现类。
2. 允许使用null键和null值,与HashSet一样,不保证映射的顺序。
3. 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
4. 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
5. 一个key-value构成一个entry
6. 所有的entry构成的集合是Set:无序的、不可重复的
7. HashMap **判断两个** **key** **相等的标准**是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
8. HashMap **判断两个** **value****相等的标准**是:两个 value 通过 equals() 方法返回 true。

HashMap键值对key-value的理解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bX6JCBpG-1652193222253)(C:\Users\Think\AppData\Roaming\Typora\typora-user-images\image-20220510195957853.png)]

  1. key是无序,不可重复的,有Set来存储
  2. value是无序,可重复的

HashMap的存储结构和底层实现原理

  1. JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJRFVnB4-1652193222261)(C:\Users\Think\AppData\Roaming\Typora\typora-user-images\image-20220510194811859.png)]

    ----JDK1.7实现原理步骤
    Map map = new HashMap<>();
    1,在实例化后,底层创建了一个长度为16的数组 Enity[16] entity
    map.put(key,value)
    1,首先调用key所在类的hashCode()方法,计算出哈希值,然后经过特定的算法,得到key在entity数组中的存放位置
    2,如果该位置上没有数据,则直接将key-value添加到该位置上 ---情况1
    3,如果该位置上有数据(存在一个或者多个数据),比较当前key哈希值和已存在的一个或者多个数据的key1,keyn的哈希值
        1,如果key的哈希值和已经存在的key1,keyn的哈希值都不相同,则直接添加成功 ---情况2
        2,如果key的哈希值和已经存在的key1,keyn的哈希值有相同的,则需要调用key所在类的equals(key1,keyn)进行比较
        	1,如果返回false:则key添加成功 -----情况3
        	2,如果返回true:则value将替换value1或者valuen
    
    情况1:key按照数组的结构存储
    情况2:key按照链表的结构存储
    情况3:key按照链表的结构存储  
    
    在不断添加元素的过程中(超出临界值并且存放的非空)时,进行扩容,默认的扩容方式:扩容为原来容量的2倍,并且将原来的数据复制过来            
    
    --底层结构概述
    1HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时,
    系统会创建一个长度为CapacityEntry数组,这个长度在哈希表中被称为容量
    (Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个
    bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
    
    2,每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引
    用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。
    而且新添加的元素作为链表的head。
       
    添加元素的过程:
    向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据
    key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数
    组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。如果位置i上
    已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次
    比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。如果
    hash值相同,继续比较二者是否equals。如果返回值为true,则使用entry1的value
    去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都
    为false,则entry1仍可添加成功。    
        
    HashMap的扩容
    当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的
    长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在
    HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算
    其在新数组中的位置,并放进去,这就是resize。
        
    
    1. JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znfYMStb-1652193222263)(C:\Users\Think\AppData\Roaming\Typora\typora-user-images\image-20220510194846590.png)]

      ----JDK1.8实现原理步骤
      Map map = new HashMap<>();
      1,在new HashMap<>()的时候,底层没有创建一个默认长度为16的数组
      2,jdk1.8底层代码数组名字为 Node[],jdk1.7底层代码数组为Entry[]
      3,首次调用put()方法的时候,底层创建了一个长度为16的数组
      4,jdk1.8底层结构为:数组+链表+红黑树,jdk1.7底层结构为:数组+链表
      5,当数组某个位置上链表的长度大于8并且当前数组的长度大于64的时候,此时链表的结构该为红黑树结构      
      
--底层原理概述
HashMap的内部存储结构其实是数组+链表+树的结合。当实例化一个
HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系
时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表
中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为
“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查
找bucket中的元素
    
每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带
一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能
生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象
可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个
TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。
    
那么HashMap什么时候进行扩容和树形化呢?
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数
size)*loadFactor 时 , 就会进行数组扩容 , loadFactor 的默认 值 (DEFAULT_LOAD_FACTOR)0.75,这是一个折中的取值。也就是说,默认
情况下,数组大小(DEFAULT_INITIAL_CAPACITY)16,那么当HashMap中
元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)
的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元
素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知
HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
    
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有
达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成
树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,
下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
    
JDK1.8相较于之前的变化:
1.HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
2.当首次调用map.put()时,再创建长度为16的数组
3.数组为Node类型,在jdk7中称为Entry类型
4.形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
5.当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置
上的所有key-value对使用红黑树进行存储。    

负载因子值的大小,对HashMap有什么影响

  • 负载因子的大小决定了HashMap的数据密度。

  • 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降

  • 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间

HashMapJDK8源码分析

  1. 调用无参构造器

      public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
    //初始化了加载因子:DEFAULT_LOAD_FACTOR=0.75
    
  2. 调用put()方法

    public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)//第一次添加元素的时候Node<K,V>[] tab为空
                n = (tab = resize()).length;//创建底层Node<K,V>[] tab数组
            if ((p = tab[i = (n - 1) & hash]) == null)//直接添加
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;//新增的元素的hash和equals()相等,直接替换
                else if (p instanceof TreeNode)//判断是否为红黑树
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {//如果不为空,并且不为红黑树的情况
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {//如果下一个位置为空,直接添加到当前节点的next
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        //如果当前节点哈希值相等,则退出循环,直接替换
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    

    概念点:

    --DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
    MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30
    --DEFAULT_LOAD_FACTOR:HashMap的默认加载因子
    TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树
    UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
    MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的
    数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行
    resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
    table:存储元素的数组,总是2的n次幂
    entrySet:存储具体元素的集
    size:HashMap中存储的键值对的数量
    modCount:HashMap扩容和结构改变的次数。
    --threshold:扩容的临界值,=容量*填充因子
    loadFactor:填充因子
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值