• Java集合(十)实现Map接口的HashMap


    Java集合(十)继承Map接口的HashMap

    一、HashMap简介(基于JDK1.8)

    HashMap是基于哈希表(散列表),实现Map接口的双列集合,数据结构是“链表散列”,也就是数组+链表 ,key唯一的value可以重复,允许存储null 键null 值,元素无序。JDK1.8对HashMap进行一个大的优化,底层数据结构有“数组+链表”的形式,变成“数组+链表+红黑树”的形式,当链表长度超过阈值时,将链表转换为红黑树,这样大大减少了查找时间。

    HashMap 的实例有两个参数影响其性能:“初始容量” 和 “负载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
    通常,默认负载因子是 0.75F, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

    (一)、HashMap与Map接口的关系


    (二)、数据结构

    JDK 1.8 的 HashMap 的数据结构如下图所示,当链表节点较少时仍然是以链表存在,当链表节点较多时(大于8)会转为红黑树。


    二、HashMap的继承结构

    从HashMap继承结构和HashMap与Map接口关系图,可以看出:

      • HashMap继承于AbstractMap类,实现了Map接口。Map是"key-value键值对"接口,AbstractMap实现了"键值对"的通用函数接口。
      • HashMap是通过"拉链法(链地址法)"实现的哈希表。
      • HashMap实现了Cloneable接口,即实现了clone()方法clone()方法的作用很简单,就是克隆一个HashMap对象并返回。
      • HashMap实现Serializable接口,分别实现了串行读取、写入功能。
        • 串行写入函数是writeObject(),它的作用是将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中。
        • 而串行读取函数是readObject(),它的作用是将HashMap的“总的容量,实际容量,所有的Entry”依次读出。

    三、HashMap的构造方法

    四、HashMap重要成员属性

    (一)、table

    是一个Node<K,V>[]数组类型,Node<K,V>实现了Map.Entry接口,是链表的节点。哈希表的"key-value键值对"都是存储在Node<K,V>数组中的。 

    (二)、size

    是HashMap的大小,它是HashMap保存的键值对的数量。

    (三)、threshold

    是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值="容量 * 负载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。

    (四)、loadFactor

    负载因子。当HashMap达到阈值时,负载因子 * 容量将HashMap扩容。

    (五)、modCount

    记录HashMap修改的次数,主要用来是用来实现fail-fast机制的。

    五、HashMap的遍历

    (一)、通过entrySet方法遍历HashMap的键值对

    1、通过entrySet()方法获取HashMap“键值对”集合Set;

    2、通过Iterator迭代器遍历获取的HashMap的“键值对”集合Set。

    1 Set<Map.Entry<K,V>> set = map.entrySet();
    2 Iterator iterator = set.iterator();
    3 while(iterator.hasNext()) {
    4     Object obj = iterator.next();
    5 }
    View Code

    (二)、通过keySet方法遍历HashMap的键

    1、通过keySet()方法获取HashMap“键”的Set集合;

    2、通过Iterator迭代器遍历获取的HashMap的“键”集合Set。

    1 Set<Map.Entry<K, V>> set = map.keySet();
    2 Iterator iterator = set.iterator();
    3 while(iterator.hasNext()) {
    4     Object obj = iterator.next();
    5 }
    View Code

    (三)、通过value方法遍历HashMap的值

    1、通过value()方法获取HashMap“值”的Set集合;

    2、通过Iterator迭代器遍历获取的HashMap的“值”集合Set。

    1 Collection coll = map.values();
    2 Iterator iterator = coll.iterator();
    3 while(iterator.hasNext()) {
    4     Object obj = iterator.next();
    5 }
    View Code

    六、HashMap常用API

    七、总结

    1、HashMap 的底层是个 Node 数组(Node<K,V>[] table),在数组的具体索引位置,如果存在多个节点,则可能是以链表或红黑树的形式存在。

    2、HashMap 的默认初始容量(capacity)是 16,capacity 必须为 2 的幂次方;默认负载因子(load factor)是 0.75;实际能存放的节点个数(threshold,即触发扩容的阈值)= capacity * load factor。

    3、HashMap 有 threshold 属性和 loadFactor 属性,但是没有 capacity 属性。初始化时,如果传了初始化容量值,该值是存在 threshold 变量,并且 Node 数组是在第一次 put 时才会进行初始化,初始化时会将此时的 threshold 值作为新表的 capacity 值,然后用 capacity 和 loadFactor 计算新表的真正 threshold 值。

    4、当同一个索引位置的节点在增加后达到 9 个时,并且此时数组的长度大于等于 64,则会触发链表节点(Node)转红黑树节点(TreeNode),转成红黑树节点后,其实链表的结构还存在,通过 next 属性维持。链表节点转红黑树节点的具体方法为源码中的 treeifyBin 方法。而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容。

    5、当同一个索引位置的节点在移除后达到 6 个时,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。红黑树节点转链表节点的具体方法为源码中的 untreeify 方法。

    6、HashMap 是非线程安全的,在并发场景下使用 ConcurrentHashMap 来代替。

  • 相关阅读:
    leetcode108 Convert Sorted Array to Binary Search Tree
    leetcode98 Validate Binary Search Tree
    leetcode103 Binary Tree Zigzag Level Order Traversal
    leetcode116 Populating Next Right Pointers in Each Node
    Python全栈之路Day15
    Python全栈之路Day11
    集群监控
    Python全栈之路Day10
    自动部署反向代理、web、nfs
    5.Scss的插值
  • 原文地址:https://www.cnblogs.com/lingq/p/12771080.html
Copyright © 2020-2023  润新知