HashMap
数据结构:数组{链表,链表,链表}
JDK1.8之后对于HashMap进行了优化
1.8结构
初始时:数组{链表,链表,链表}
当链表增长到8个元素时链表转化为红黑树:数组{链表(–>红黑树)链表(–>红黑树)}
HashMap初始化
hashMap初始化的时候会构建存储大小的阈值和扩容临界值,当hashmap大小接近阈值时,就会进行扩容,hashmap的大小是2的幂数,所以扩容也是按照当前大小×2进行扩容。如new HashMap大小是519时,那么分配给他的内存就是1024
HashMap的存储
当一个<k,v>键值对需要存储的时候,先通过hash计算取模(),根据取模结果存入数组的相应位置。
冲突:当有两个<k,v>取模结果相同时,就要存入相同的数组位置,而数组的这个位置上已经有一个<k,v>。
当数组位置冲突时会转化成一个链表,第二个<k,v>就会存在第一个<k,v>后。当链表达到一定长度时,数组就会进行扩容(链表的数据结构特性是易存不易查,长度越长查询越慢)。
红黑树实现不做描述了,java实现红黑树很繁琐这里不贴代码了。红黑树的转换原理是当单个链表达到8那么这个链表就转化成红黑树,而不会转化数组中其他链表。
ConcurrentHashMap
JDK1.7底层:数组{数组segment{entry,entry},数组segment{entry},…}
JDK1.8底层:数组{链表}
JDK1.7ConcurrentHashMap操作原理
在1.7时ConcurrentHashMap不会扩容,初始化时就已经确定了segement的数量,多线程并行操作ConcurrentHashMap时每个线程操作一个segment数组,当线程操作segment时会lock这个segment,所以ConcurrentHashMap是安全的。segment的作用相当于一个加锁(分段锁模式)的hashMap,这个hashMap不会又转化红黑树的操作,segment是可以扩容的。
JDK1.8ConcurrentHashMap操作原理
JDK1.8弃用了segment,使用node<k,v>来实现数组内部元素。
node元素初始化
node初始化的时候,会有一个位置信息的控制权(SIZECTL),当有线程的hash值=node元素位置,而这个数组的node节点刚好为空,那么需要所有要操作这个node元素的线程争抢SIZECTL,只有一个线程可以抢到,所以只有一个线程可以操作node,这个过程使用了CAS自旋操作,但是并不会消耗大量的性能,因为没有抢到SIZECTL的线程会使用Thread.yield()方法来释放资源,当线程1初始化node完成之后,其他线程则不会直接break跳出初始化。
node位置冲突时的解决办法
当两个key的hash值都对应一个node时,node只允许一个key进行put操作,此时会使用synchronized的锁住这个node链表的head节点,另一个key的线程则等待(链表是key1–next–>key2–next–>key3所以锁住head之后相当于锁住整个链表,因为无法从头遍历)。当链表达到一定数量之后还是会转化成红黑树,每次put元素后都会比对阈值,当达到阈值时就会执行扩容。扩容时会使用CAS机制来保证线程的安全性。
JDK1.8ConcurrentHashMap使用锁总结
当put时,如果key1、key2不发生冲突,则使用CAS去初始化node。如果key1、key2冲突,则使用synchronized同步代码块来锁定node的head节点。
ConcurrentSkipListMap跳表
ConcurrentSkipListMap的实现原理:有序链表;无锁;value不能为空;层级越高跳跃性越大,数据越少,查询理论变快。
查找数据时,按照从上到下,从左到右顺序查找,是空间换时间,类似数据库索引的概念,在一些开元组件中有使用(level DB、redis)
插入数据的底层实现原理:首先比对key(这个key不是Map里的key,而是节点)值,对于key进行排序,然后将key插入到指定位置如下图
每个node都有机会升级为索引,升级原则为随机生成。
新的node是否抽取出来作为index,随机决定;index对应的level也有随机数决定。每层元素headindex固定为所有node中最小的;
索引内部属性如下图node(当前节点)、right(右侧节点)、down(下层节点)
整个ConcurrentSkipListMap只有一个入口就是链表的head index入口
链表实现map
public class SkipMapDemo {
Node head = null; // 队列的头部
public void add(Node node) {
if (head == null) {
head = node;
return;
}
// 追加到队列尾部
Node temp = head;
while (temp.next != null) {
// 找到一个next为空的节点,这个节点就是最后一个
temp = temp.next;
}
temp.next = node;
}
public void print() {
// 打印链表内容
Node temp = head;
while (temp.next != null) {
System.out.println(temp.key);
temp = temp.next;
}
System.out.println(temp.key);
}
public static void main(String[] args) throws IOException {