【手撕Java集合】Map容器概述以及常用子类HashMap、LinkedHashMap详解(附部分源码)

一、Map 集合概述

1. 什么是 Map?

Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象。其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素还可以是数组一样。

如果需要存储几百万个学生,而且经常需要使用学号来搜索某个学生,那么这个需求有效的数据结构就是Map。

1629788940349

2. Map 和 Collection 的区别

  • Map 与 Collection 在集合框架中属并列存在。

  • Map 存储的是键值对,而 Collection 只存储值(也就是Collection是单列集合, Map 是双列集合)

  • Map 存储元素使用 put 方法,Collection 使用 add 方法。

  • Map 集合没有直接迭代元素的方法,而是先转成 Set 集合(entrySet),再通过迭代获取元素

  • Map 集合中键要保证唯一性,值是可以重复的。

3. Map 的结构体系

1630048100635

4. Map 的基础功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VeF1Ukp6-1645777192905)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636783331643.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qjINltBA-1645777192909)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636783405837.png)]

5. Map 集合常用子类

  • HashMap

    • 无序。
    • 底层是 散列表(数组+链表)+红黑树 数据结构。
    • 线程是不同步的,可以存入 null 键,null 值。
    • 要保证键的唯一性,需要覆盖 hashCode 方法和 equals 方法。
  • LinkedHashMap

    • 底层是 散列表+双向链表。可以 Map 集合进行增删提高效率。
    • 插入顺序是有序的。
    • 允许为 null,不同步
  • HashTable

    • 底层是 散列表
    • 同步(线程安全),但效率较低。
    • key 和 value 都不允许为 null。
  • TreeMap

    • 底层是 红黑树。方法的复杂度大都是 log(n)。
    • 非同步
    • TreeMap 实现了 NavigableMap 接口,而 NavigableMap 接口继承了 SortedMap 接口,致使 TreeMap 是有序的。
    • 使用 Comparator 或者 Comparable 来比较 key 是否相等与排序问题。
  • ConcurrentHashMap

    • 底层是 散列表+红黑树
    • 同步(线程安全),支持高并发的访问和更新。
    • key 和 value 都不允许为 null。
    • 检索操作不用加锁,get 方法是非阻塞的。

二、HashMap

类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znqwnf1m-1645777192910)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636784047067.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tyuVIcW-1645777192913)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636784278422.png)]

由类注释可以归纳出 HashMap 的特点:

  • 允许为 null, 但 null 作为键只能有一个,null 作为值可以有多个
  • 非同步,无序
  • 底层是 散列表+(红黑树)
  • 初始容量和装载因子都会影响迭代性能

JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap 总是使用 2 的幂作为哈希表的大小。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hAmZHGJC-1645777192915)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636889496356.png)]

1. HashMap 的属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZeQTdnL-1645777192917)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636785081539.png)]

成员属性有如下几个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3LucGppY-1645777192919)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636785213649.png)]

属性详解:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
   
    // 序列号
    private static final long serialVersionUID = 362498820763181265L;
    // table数组默认的初始容量是16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    // table数组最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    // 默认的填充因子
    /**
     *默认的负载因子是0.75f,也就是75% 负载因子的作用就是计算扩容阈值用,比如说使用
     *无参构造方法创建的HashMap 对象,他初始长度默认是16  阈值 = 当前长度 * 0.75  就
     *能算出阈值,当当前长度大于等于阈值的时候HashMap就会进行自动扩容
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 当桶(bucket)上的结点数大于这个值时会转成红黑树
    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;
    // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
    /**
    * threshold就是在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。
    */
    int threshold;
    // 加载因子
    final float loadFactor;
}

HashMap 中还有一个内部类 Node,用于存放散列表下的链表结构中的节点元素:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6w66Zxl-1645777192921)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636785301797.png)]

2. HashMap 的构造器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UCFUToDl-1645777192922)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636890733965.png)]

HashMap(int, float)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aa7r9EHv-1645777192923)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636785609555.png)]

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     * 用确定的初始化容量和装载因子构造一个HashMap
     *
     * @param  initialCapacity the initial capacity 初始化容量
     * @param  loadFactor      the load factor 装载因子
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
   
        if (initialCapacity < 0) //初始容量不能为0
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY) //容量大于最大允许容量(2^30)
            initialCapacity = MAXIMUM_CAPACITY; //赋为最大允许容量
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) //装载因子不能非法
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor; //赋为默认装载因子
        this.threshold = tableSizeFor(initialCapacity); 
    }

其中最后一行调用的 tableSizeFor() 方法如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1zNoiopR-1645777192925)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636786567154.png)]

为啥是将2的整数幂的数赋给threshold

threshold 这个成员变量是阈值,决定了是否要将散列表再散列。它的值应该是: capacity * loadfactor

其实这里仅仅是一个初始化,当创建哈希表的时候,它会重新赋值的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dkrQDGIf-1645777192926)(https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1636786763891.png)]

另外几个构造器:

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     * 指定容量大小的构造函数,装载因子为默认值0.75
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
   
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     * 默认构造函数,容量为16,装载因子为0.75
     */
    public HashMap() {
   
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     * 包含另一个“Map”的构造函数
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
   
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

3. put 方法

    public V put(K key, V value) {
   
        return putVal(hash(key), key, value, false, true);
        //以key计算哈希值,传入key、value还有两个参数
    }

HashMap 只提供了 put 用于添加元素,putVal 方法只是给 put 方法调用的一个方法,并没有提供给用户使用。

对 putVal 方法添加元素的分析如下&#

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kaho Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值