首页 > 编程语言 >Java 集合框架5:Map

Java 集合框架5:Map

时间:2022-12-02 01:11:07浏览次数:42  
标签:Map Java HashMap 位置 线程 table 集合 hash

目录

Map

1.概述

Map 是一个将 key 映射到 value 的对象,key 不能相同,而且最多映射一个 value。Map 取代了 Dictionary 抽象类。Map 提供了 3 个集合视图:keySet、entrySet 以及 values。Map 的顺序定义为迭代器的顺序,但有些实现类,如 TreeMap,有自己的顺序保证。

2.SortedMap

与 SortedSet 类似,相比Map接口提供了 Range View、Endpoints 和 Comparator access 的额外操作。

3.实现

Map 主要有四个常用的实现类,分别是 HashMap、HashTable、LinkedHashMap 和 TreeMap,类的继承关系如下:

它们的特点如下:

  1. HashMap:它根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻如果有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。
  2. Hashtable:Hashtable 是遗留类,很多 Map 的常用功能与 HashMap 类似,不同的是它继承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。
  3. LinkedHashMap:LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
  4. TreeMap:TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历TreeMap 时,得到的记录是排过序的。如果使用排序的 Map,建议使用 TreeMap。在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造TreeMap 传入自定义的 Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
  5. 对于上述四种Map类型的类,要求 Map 中的 key 是不可变对象。不可变对象在创建后它的哈希值不会被改变。如果对象的哈希值发生变化,Map对象很可能就定位不到映射的位置了。
  6. Map 提供了三种特殊用途的Map实现:EnumMap、WeakHashMap 和 IdentityHashMap。EnumMap 与 EnumSet 类似,主要用于 enum 类型。WeakHashMap 用于存储键的 weak reference,当键值对在 WeakHashMap 之外不存在引用后,GC 可以对 WeakHashMap 中键值对进行垃圾回收

此外,有一个特殊的 HashMap 实现,IdentityHashMap。IdentityHashMap 实现了 Map 接口,在内部实现时,使用了 reference-equality 而不是 object-equality 进行相等比较,也就是说,在 IdentityHashMa p中,如果两个键 k1 和 k2 相等,那么当且仅当 k1 == k2,而在 HashMap 中相等的条件是k1 == null ? k2 == null : k1.equals(k2)。IdentityHashMap 主要于对需要保留拓扑结构的图转换中,例如序列化或者深拷贝。在 Calcite 的代码中,需要对 RelNode 的属性各种变换,但是不改变 RelNode DAG 的结构,每个节点依然复用原来的 RelNode,所以会经常使用 IdentityHashMap。

HashMap

HashMap 提供了所有 Map 的可选方法。HashMap 类似于 Hashtable,但与它不同的是,HashMap 允许 null 作为 key 和 value,并且 HashMap 并不是线程安全的。

HashMap 获取迭代器的时间与 HashMap 的实际容量加上它的大小成正比,所以,当对迭代器的要求比较高时,不要将 HashMap 的初始容量设置太大,load factor 也不要设置得太小。

hash table 中每个位置表示为一个桶 bucket,容量 capacity 表示 bucket 的数量,也就是 hash table 的长度。负载因子 load factor 表明 capacity 能使用的多少(该值可以大于 1)。HashMap 中将 key-value 对封装为一个 Node(继承于 Map.Entry)。当 hash table 中的数量超过阈值 threshold (等于 capacity * loadFactor) 时,将会进行 rehash 操作 (也就是重新构建结构),hash table 的大小会变为原来的两倍,并修改原来 Entry 存放的位置。

默认的 load factor 为 0.75 ,对时间和空间的花费会达到一个较好的情况。高 load factor 虽然会减少空间开销,但会增加查找成本。如果初始化的 capacity 大于 entry 的最大数量除以 load factor,则不会 rehash。

HashMap 并不是线程安全的,它的迭代器也具有快速失效的特点。

HashMap 的最大 bucket 数量为 1 << 30。

实现原理

HashMap 的实现基于 hash table,本质上是一个数组。数组上每个位置被称为一个桶。

注意:table 的大小为 2 的幂,默认大小为 16。而 hash table 的大小最好为素数,这是因为合数大小的 hash table,如果采用求余数的方法来算哈希值(这里假设根据数 k 来算哈希值),那么对于出现哈希冲突的情况下, k 的二进制的某些位不一致影响并不大。举个例子来说,对 20、28 求 8 的余数,均为 4,出现哈希冲突,而 20、28 的二进制 10100、11100 的大于第 3 位的数不一致,对哈希值的运算也没有影响,但如果把 8 改为 11,那么有影响的位就多了。HashTable 的默认大小为 11 就是一个例子。至于为什么 HashMap 并没遵守这个原则,在于 HashMap 算桶的位置方法除了取模,也有高位运算的方法。

HashMap 解决哈希冲突的方式链地址方法。桶中存储的可能为一个 Entry,但当出现哈希冲突时,桶中存储的是一个链表结构,但当该链表过长时(默认为 8),就会转为红黑树,提高 HashMap 的性能。

相反的,红黑数过短(默认为 6),会转换为链表。

但需要注意的是,需要满足 bucket 数量大于 MIN_TREEIFY_CAPACITY (默认为 64)才会进行数化,否者采用 resize。

接下来我们查看 put 方法是如何实现的。

put 方法会调用 putVal 方法。

其中传参时,第一参数是调用了 hash 方法。key 为 null 时返回 0,否则,hash 方法会将 key 的 hashCode 与它的高 16 位进行异或操作,返回得到的结果,这样减少 hash table 长度为 2 的幂导致的高位无影响。

在 putVal 方法中,tab 为底层的 hash table,n 为 tab 长度,i 为应该应该加入的 bucket 位置(取模操作),p 用于查找新 Node 应该放置的位置(并不是应该放置的位置)。

通过 put 例子,我们可以看出,计算 bucket 位置的流程,示例如下:

而对于 put 方法的实现流程,可用下图概括:

接下来我们看看 rehash 是如何实现的。

rehash 中主要使用到了 resize 方法。resize 方法会开辟一个两倍原始大小的 table 并把原 table 的元素迁移过去,而这个方法中,最重要过程是迁移。由于确定 Node 在那个 bucket 的位置只是取模决定的(取模用到的值是高位运算后的值,而高位运算用到的 hash 函数并没有牵涉到 table 长度),table 变为两倍,那么 Node 在新 bucket 的位置要么在原索引位置 k,要么在 k + oldCap 位置,这取决于:新的 capacity - 1上最高的 1 的位置 p,hash 函数返回值的 p 位置为 0 还是 1。为 0 则在原位置,否则在新位置。基于此,我们只需要查看那一位的情况即可确认位置,而不用全部通过高位运算以及取模运算来计算新位置。

这是对于链表:

这是对于红黑树:

Hashtable

Hashtable 是线程安全的,但是其实现并发安全的手段比较粗暴,只是简单的以自身作为对象锁,将相关方法都声明为synchronized,故每次只有一个线程能调用这些同步方法。

Hashtable 不允许 null key 或 null value。

ConcurrentHashMap

// TODO 02/12/2022 meyok

标签:Map,Java,HashMap,位置,线程,table,集合,hash
From: https://www.cnblogs.com/meyok/p/16943256.html

相关文章

  • 目标检测模型的评价标准-AP与mAP
    目录目录目录前言一,精确率、召回率与F11.1,准确率1.2,精确率、召回率1.3,F1分数1.4,PR曲线1.4.1,如何理解P-R曲线1.5,ROC曲线与AUC面积二,AP与mAP2.1,AP与mAP指标理解......
  • Java实现在线SQL编程【完整版】
    前言:由于前段时间,项目组长分配的任务是要完成一个在线编写​​​SQL​​​并要实现查询功能的需求,最终需要将查询到的数据以​​JSON​​​格式显示到响应数据的区域,以供操......
  • jmaps
    #!/bin/bash##jmaps-createsjava/tmp/perf-PID.mapsymbolmapsforalljavaprocesses.##Thisisahelperscriptthatfindsallrunning"java"processes,......
  • JavaWeb项目练习(学生选课管理系统)二【新建数据库】
    思路1、页面美化css这部分,挖个坑,我打算做好一点所以先空着。×2、需要做四个数据表(学生、教师、管理员、课程)关联:学生有个人课表教师有教授课程和个人课表管理员有全......
  • centos 7.9安装java和java环境配置(超实用)
    一、安装包准备安装包,可以通过下面地址进行版本选择安装https://www.oracle.com/java/technologies/downloads/#java8二、正式开始安装本次分享的安装方法直接通过编辑......
  • Spring高级应用之注入各类集合
    先定义一个测试类,由于本文将要介绍注入各种集合时如何配置,故这个类包含各种集合,类名和属性名不好取,没有特殊含义:1publicclassTest{2privateList<String>......
  • java 中的Stack、Queue、Deque
    1.Stackjava集合框架中没有Stack接口,仅仅有java早期遗留下来的一个Stack类。Dequestack=newArrayDeque();publicStackextendsVector因为集成自Vector,所以Stack类是......
  • Java学习十一
    1.可以从现有的类派生出新类。这称为类的继承。新类称为次类、子类或派生类。现有的类称为超类、父类或基类。2.构造方法用来构造类的实例。不同于属性和方法,子类不继承父......
  • Java 中经常被提到的 SPI 到底是什么?
    Java程序员在日常工作中经常会听到SPI,而且很多框架都使用了SPI的技术,那么问题来了,到底什么是SPI呢?今天阿粉就带大家好好了解一下SPI。SPI概念SPI全称是Service......
  • Java运算符
    运算符算术运算符+;-;*;/;%;+;--; publicstaticvoidmain(String[]args){     inta=10;     intb=20;     intc=25;    ......