• 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 来代替。

  • 相关阅读:
    路由网址这是mvc时代系列之三:网络路由与ASP.NET MVC生命周期(上)
    调用代码JCFXBL与WebView整合打造安卓应用服务平台
    服务安装Mongodb应用实战教程:windows下安装与安全策略配置
    方法选择Android从SD卡中选择图片的三种方法及自定义系统选择框标题的方法
    图层照片如何扣头发丝
    对象方法JavaScript脚本语言初解
    数据库方法django中ModelForm学习系列一~save方法
    团队位置《楚汉传奇》聊管理
    包查找*.so对应的rpm包
    现实世界的Windows Azure:就Metanga采访MetraTech公司CEO,Scott Swartz先生
  • 原文地址:https://www.cnblogs.com/lingq/p/12771080.html
Copyright © 2020-2023  润新知