2、Map(K-V 对)
2.1、概述
-
a、HashMap( HashMap 的实现原理?)
底层数据结构,JDK 1.8 是数组 + 链表 + 红黑树,JDK 1.7 无红黑树。链表长度大于 8 时,转化为红黑树,优化查询效率。
初始容量为 16,通过 tableSizeFor 保证容量为 2 的幂次方。寻址方式,高位异或,(n-1)&h 取模,优化速度。
扩容机制,当元素数量大于容量 x 负载因子 0.75 时,容量扩大为原来的 2 倍,新建一个数组,然后转移到新数组。 -
b、基于 Map 实现。
线程不安全。
-
c、HashMap (1.7) 多线程循环链表问题
在多线程环境下,进行扩容时,1.7 下的 HashMap 会形成循环链表。
怎么形成循环链表: 假设有一 HashMap 容量为 2 , 在数组下标 1 位置以 A -> B 链表形式存储。有一线程对该 map 做 put 操作,由于触发扩容条件,需要进行扩容。这时另一个线程也 put 操作,同样需要扩容,并完成了扩容操作,由于复制到新数组是头部插入,所以 1 位置变为 B -> A 。这时第一个线程继续做扩容操作,首先复制 A ,然后复制 B ,再判断 B.next 是否为空时,由于第二个线程做了扩容操作,导致 B.next = A,所以在将 A 放到 B 前,A.next 又等于 B ,导致循环链表出现。 -
d、HashTable
线程安全,方法基本全用 Synchronized 修饰。
初始容量为 11 ,扩容为 2n + 1 。
继承 Dictionary 类。 -
e、ConcurrentHashMap
线程安全的 HashMap。
1.7 采用分段锁的形式加锁;1.8 使用 Synchronized 和 CAS 实现同步,若数组的 Node 为空,则通过 CAS 的方式设置值,不为空则加在链表的第一个节点。获取第一个元素是否为空使用 Unsafe 类提供的 getObjectVolatile 保证可见性。
对于读操作,数组由 volatile 修饰,同时数组的元素为 Node,Node 的 K 使用 final 修饰,V 使用 volatile 修饰,下一个节点也用 volatile 修饰,保证多线程的可见性。 -
f、LinkedHashMap
LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。 -
TreeMap
有序的 Map,红黑树结构,可以自定义比较器来进行排序。
-
h、Collections.synchronizedMap 如何实现 Map 线程安全?
基于 Synchronized ,实际上就是锁住了当前传入的 Map 对象。
2.1.2、Map的操作
2.2、HashMap
源码分析:
首先HashMap实现了Map接口
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V