HashMapHashTable
TreeMap
ConcurrentHashMap
LinkedHashMap 有序map
内部像list一样增加了 尾部、首部指向指针
public class HashMap<K,V>extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
Correction、Set、List 接口都属于单值的操作,而 Map 中的每个元素都使用 key——>value 的形式存储在集合中。 Map 集合:该集合存储键值对。一对一对往里存。而且要保证键的唯一性
——HashMap:底层数据结构是哈希表,允许使用 null 值和 null 键,该集合是不 同步的。
——TreeMap:底层数据结构是二叉树。线程不同步。可以用于给 map 集合中的键进 行排序
基本属性:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始化大小 16
static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子0.75
static final Entry<?,?>[] EMPTY_TABLE = {}; //初始化的默认数组
transient int size; //HashMap中元素的数量
int threshold; //判断是否需要调整HashMap的容量
HashMap:
HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而 具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时 写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。我 们用下面这张图来介绍 HashMap 的结构
HashMap<Object, Object> map = new HashMap<>(2);
map.put("1",null);
map.put("1","e");
map.put("3","c");
map.put("4","d");
map.putIfAbsent("5",null);
map.putIfAbsent("5","ff");
PUT方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
/*
第一次put时进入resize()方法 ,判断是否需要初始化Map大小,记录临界值threshold ,table Map中元素
*/
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
/*
当前容量(hash桶数量)-1 & hash运算,找到存储到哪个hsah桶中,
if 如果当前位置中没有元素,直接插入进去;
else 先判断插入的key和 当前第一个节点的key是否一样
判断当前位置是否是红黑树结果
链表结构:查出当前长度 ,判断是否转为红黑树结构;判断是否有相同的key
*/
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
/*
插入元素 :判断是否需要覆盖value
putIfAbsent方法时如果key相等,第一次value==null 第二次 value!=null 也会覆盖
*/
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
resize方法
final Node<K,V>[] resize() {
/*赋值*/
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length; 扩容前的
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//扩容元素转移
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
/*
低筒 lo 构建原来索引位置 的链表,需要的指针
高筒 hi 构建 原来索引 + oldCap 位置 的链表需要的指针
*/
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
//获取当前节点e的下一个节点
next = e.next;
//判断e的哈希值和之前桶数求余是否为0,如果为0说明肯定还会在原来的桶中
if ((e.hash & oldCap) == 0) {
//如果为0,且之前的节点==null
if (loTail == null)
//设置低筒的头节点为e
loHead = e;
else
//设置低筒的尾下一个节点为e
loTail.next = e;
//设置尾节点为e
loTail = e;
}
//如果不为0,说明不会在低筒,会在新加入的桶中
else {
//如果高桶的尾结点为空
if (hiTail == null)
//说明高桶的头结点为e
hiHead = e;
else
//如果高桶为节点不为空,
//尾结点的下一个是e
hiTail.next = e;
//设置尾结点为e
hiTail = e;
}
} while ((e = next) != null);
//如果低筒不为null
if (loTail != null) {
//低筒的下一个不为null,设置低筒的为节点的next指针为null
loTail.next = null;
//将低筒中的元素放进桶中
newTab[j] = loHead;
}
//如果高桶不为null
if (hiTail != null) {
//先将高桶的尾指针的next指针置为null
hiTail.next = null;
//直接将串好的链放入高桶中
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
remove
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) { // 获取索引,
Node<K,V> node = null, e; K k; V v;
//判断当前位置首节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) // 判断索引处的值是不是想要的结果
node = p;
else if ((e = p.next) != null) { // 交给树的查找算法
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do { // 遍历查找
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode) //树的删除
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) // 修复链表,链表的删除操作
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
ConcurrentHashMap
Segment 段
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以 要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分”或” 一段“的意思,所以很多地方都会将其描述为分段锁。注意,行文中,我很多地方用了“槽” 来代表一个 segment。
线程安全(Segment 继承 ReentrantLock 加锁)
简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保 证每个 Segment 是线程安全的,也就实现了全局的线程安全。
并行度(默认 16)
concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。 默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候, 最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这 个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体 到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安 全,所以处理起来要麻烦些。
Java8 实现 (引入了红黑树) Java8 对 ConcurrentHashMap 进行了比较大的改动,Java8 也引入了红黑树