java 实现hashmap_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接口的关系

9bd0bf4f37d02514585307b5ee23b1a5.png

(二)、数据结构

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

62036ff4137138bad199cb7110b2e59a.png

二、HashMap的继承结构

dad4b0b07ffcdaba880f07cb222db19a.png

cb48bf4b74464c068d4890c19a60f25e.png

从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的构造方法

6bd64a704dd9e0fb9cb129830d354eb7.png

四、HashMap重要成员属性

(一)、table

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

(二)、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。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 Set> 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。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 Set> 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。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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

View Code

六、HashMap常用API

01114d6c0c9609d6d0ab9c4f4c59ca66.png

七、总结

1、HashMap 的底层是个 Node 数组(Node[] 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 来代替。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值