HashMap

本文详细介绍了HashMap的工作原理,包括其哈希表实现、扩容机制、put方法的实现步骤。同时,讨论了HashMap在JDK1.8中引入红黑树的原因,以解决链表过长导致的性能下降问题。此外,对比了HashMap与线程安全的HashTable的区别。最后,提到了ConcurrentHashMap的实现方式,从JDK1.7的Segment到JDK1.8的细粒度锁策略的演变。
摘要由CSDN通过智能技术生成

==hashMap概述
   HashMap是基于哈希表的map接口的非同步实现,此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
hashMap的特性
1.HashMap是Map接口的一个实现,用来实现键值对的快速存取,key,value都可为null,key值不可重复,重复则后来者覆盖。
2.不保证有序。
3.HashMap的实现不是同步的,这意味着它不是线程安全的。

HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?
(1)扩容时机:
初始化数组(jdk1.8之后,默认在第一次插入数据的时候才进行数组初始化,初始化是通过调用扩容方法实现的);
当元素个数超过临界值(临界值=装载因子*数组容量);
当链表长度超过默认阈值8,尝试转化为红黑树,但发现数组长度不到64时。
(2)扩容机制:
如果数组未初始化过,会将数组的容量和装载因子都设置为默认值,并将数组创建出来。
如果数组初始化过,扩容会分配一个新的数组,新的数组长度翻倍,然后遍历整个老结构将元素重新哈希映射到新的数组里。
HashMap在进行扩容时,使用的重新哈希的方式非常巧妙,因为每次扩容都是翻倍,与原来计算的 (数组长度-1)&hash 的结果相比,只是多了一个bit位,所以结点要么就在原来的位置,要么就被分配到"原位置+旧容量"这个位置。

HashMap 的工作原理?
HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry接口)实现,HashMap 通过 put & get 方法存储和获取。

存储对象时,将 K/V 键值传给 put() 方法:

①、调用 hash(K) 方法计算 K 的 hash 值,然后结合数组长度,计算得数组下标;

②、调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n);

③、i.如果 K 的 hash 值在 HashMap 中不存在,则执行插入,若存在,则发生碰撞;

ii.如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 true,则更新键值对;

iii. 如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。

(JDK 1.7 之前使用头插法、JDK 1.8 使用尾插法)(注意:当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树)

获取对象时,将 K 传给 get() 方法:①、调用 hash(K) 方法(计算 K 的 hash 值)从而获取该键值所在链表的数组下标;②、顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。
hashCode 是定位的,存储位置;equals是定性的,比较两者是否相等。

concurrenHashMap实现原理
  DK1.7 中的 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即 ConcurrentHashMap 把哈希桶数组切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问,实现了真正的并发访问。
其中,用 volatile 修饰了 HashEntry 的数据 value 和 下一个节点 next,保证了多线程环境下数据获取时的可见性!
JDK1.8
在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的Node数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS + synchronized实现更加细粒度的锁。
将锁的级别控制在了更细粒度的哈希桶数组元素级别,也就是说只需要锁住这个链表头节点(红黑树的根节点),就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。

3.hashMap中put是如何实现的?
分为三大步,一是计算出元素在数组中的存储索引位置,二是将数据保存到table数组中,三是修改某些成员变量的值并根据情况判断是否扩容。

①首先要根据键值key计算出哈希值,然后通过位运算实现哈希值的取模,以得到元素在数组中的索引位置。
②如果是第一次插入数据(在jdk1.8之后),还要先通过resize()方法对数组进行初始化。
①判断将要存储的数组位置是否已经存在元素,不存在则说明没有发生哈希碰撞,直接将元素添加到数组中。
②存在则说明发生了哈希碰撞,需要依次进行以下判断:
a.是否是相同的key值,是则覆盖掉旧值替换为新值;
b.是否是红黑树结构,是则直接插入;
c.以上判断均不符合,说明是链表结构,则遍历链表在尾部进行插入,在这个过程中如果检查到相同key值元素则直接替换旧值,如果插入元素后链表长度大于阈值则尝试转化为红黑树。
代表元素个数的size加一,代表修改次数的modCount加一,size超过临界值则进行扩容。

.传统hashMap的缺点(为什么引入红黑树?
JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。

对红黑树的见解?

每个节点非红即黑

根节点总是黑色的

如果节点是红色的,则它的子节点必须是黑色的(反之不一定)

每个叶子节点都是黑色的空节点(NIL节点)

从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

HashMap 和 HashTable 有什么区别?
①、HashMap 是线程不安全的,HashTable 是线程安全的;

②、由于线程安全,所以 HashTable 的效率比不上 HashMap;

③、HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许;

④、HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;

⑤、HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode


Map接口:7

Map: 双列数据,存储key - value键值对。

HashMap: 是Map的主要实现类,可以存储null的key和value;线程不安全,效率高。

TreeMap:底层使用红黑树,按添加的key - value对进行排序,所以实现Comparable接口或者重写Compartor的compare()方法。

Hashtable: Map的古老实现类,不能存储null的key - value,线程不安全,现在基本不用。

LinkedHashMap: HashMap的子类,可以按照添加的顺序遍历,因为在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。

Properties:常用来处理配置文件。key-value都是String类型。


HashMap,LinkedHashMap,TreeMap有什么区别?
HashMap参考其他问题;
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历时,先取到的记录肯定是先插入的;遍历比HashMap慢;
TreeMap实现SortMap接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值