基础
List
- 什么是List
列表
- ArrayList
底层是数组,线程不安全,查询和修改快,增删慢,通过角标操作
- LinkedList
底层是双向链表,线程不安全,查询和修改慢,增删快
- Vector
底层是数组,线程安全,操作的时候使用了synchronized进行加锁
Map
- HashMap
基于数组+链表,线程不安全,默认容量16。
可以实现快速存储和检索,适用于在map中插入删除和定位元素。
允许有空的键和值
- HashTable
基于哈希表实现,线程安全(加synchronized),默认容量11.
不允许有null键和值
- LinkedHashMap
- TreeMap
基于平衡二叉树–>红黑树。
可以自定义排序规则。但性能较HashMap差。
适用于需要自定义排序规则的情况
- ConcurrentHashMap
线程安全的HashMap
进阶
如何让ArrayList线程安全?
-
自己写一个包装类,对于修改操作进行加锁
-
Collections.synchronizedList(new ArrayList<>()); 使⽤用synchronized加锁
几乎每个方法都用到了synchronized同步锁。
写性能比CopyOnWriteArrayList好
- CopyOnWriteArrayList<>(); 使用ReentrantLock加锁
执行修改操作时,拷贝一份新的数组进行操作。修改完成后,将原来的集合指向新的集合。
使用ReentrantLock可重入锁,保证不会有多个线程同时拷贝一份数组。
用在读多写少的情况,读的时候是不加锁的,所以读性能很高。
CopyOnWriteArrayList其实是典型的读写分离,最终一致思想的一个应用。
但是存在一个问题,就是内存占用过大。在复制状态下,内存中同时有两个对象的内存,如果对象过大,
就有可能引发Yong GC和Full GC
ArrayList的扩容机制
JDK1.7之前,ArrayList默认大小10,JDK1.7之后,默认大小0.
当集合第⼀一次添加元素的时候,集合⼤大⼩小扩容为10
ArrayList的元素个数⼤大于其容量量,扩容的⼤小= 原始大小+原始大小/2,即每次扩容,都是按照当前大小的一半进行扩容。
每次扩容,都会伴随着一次数据复制。所以,能确定数据大小的情况下,尽可能指定ArrayList的size大小
hashcode和equals
HashMap的键和HashSet集合中的元素,是需要保证唯一的。
那么,所谓唯一
的依据是什么?
- hashcode
计算哈希码,即散列码
- equals
比较两个对象是否相等
当我们把对象存入HashSet、HashMap等容器的时候,hashcode和equals就派上用场了。
- 如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。
- 如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等,此时就出现所谓的哈希冲突场景。
一个好的hashCode的方法的目标:为不相等的对象产生不相等的散列码,同样的,相等的对象必须拥有相等的散列码。
深入HashMap
- 存储结构
hashcode是一个数组+链表的结构
数组中的每一项,都是一个链表
在JDK1.8中,链表长度大于8,链表会转成红黑树
Node<K,V>[] table 是数组,数组的元素是Entry(Node继承Entry),Entry元素是⼀个 key-value的键值对,
它持有一个指向下个Entry的引用,table数组的每个Entry元素同时也作为当前 Entry链表的首节点,也指向了了该链表的下个Entry元素
1、元素的key的hash值,决定了其在数组的哪个节点
2、链表结构,是为了解决hash冲突。将hash值一样的对象,存储在一个链表中、
3、jdk1.8后用红黑树代替链表,主要是为了提升查询性能。从O(n)提升到了O(logn)
- 什么是hash碰撞
key的hashcode相同的时候,就会产生hash碰撞。
相同的hashcode对应的元素,会存放到同一个bucket中。
常见的解决办法有链表法(HashMap采用)、开发地址法、再哈希法
- 什么是红黑树
https://zhuanlan.zhihu.com/p/79980618?utm_source=cn.wiz.note
在线演示:https://rbtree.phpisfuture.com
- 每个节点都有红色或黑色
- 树的根始终是黑色的 (黑土地孕育黑树根, )
- 没有两个相邻的红色节点(红色节点不能有红色父节点或红色子节点,并没有说不能出现连续的黑色节点)
- 从节点(包括根)到其任何后代NULL节点(叶子结点下方挂的两个空节点,并且认为他们是黑色的)的每条路径都具有相同数量的黑色节点
瞬间懵逼?了解一下印象就行,开始玩魔方都是要照着魔方公式一点点玩的,多玩几次就熟悉了。红黑树也一样,红黑树有两大操作:
- recolor (重新标记黑色或红色)
- rotation (旋转,这是树达到平衡的关键)
- hashcode
根据key的hashcode,可以实现快速定位
但是,hashmap的元素hash值,并不是直接就由key的hashcode算法得到的。而是对key的hashcode进行二次运算
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
如上,为key的算法。即 key的hashcode异或h>>>16.(>>>为无符号右移)。
之所以这么做,是为了加大离散程度。让key的分布更均匀
- 为啥用红黑树而不是其他树(如:二叉平衡树)
二叉查找树在特殊情况下也会变成一条线性结构,和原先的链表存在一样的深度遍历问题,查找性能 就会慢,
使用红黑树主要是提升查找数据的速度,红⿊树是平衡二叉树的一种,插⼊新数据后会通过左旋,右旋、变色等操作来保持平衡,
解决单链表查询深度的问题
- 为啥等长度到8之后才变化为红黑树
数据量少的时候操作数据,遍历线性表比红黑树所消耗的资源少,且前期数据少 平衡二叉树保持平衡是需要消耗资源的,所以前期采⽤线性表,等到一定数之后变换到红黑树。
其实还有一个条件,即 当hashmap的散列数组长度达到64
- put流程
- get流程
ConcurrentHashMap
ConcurrentHashMap是采⽤了分段锁的思想提高性能,锁粒度更细化
什么是分段锁:https://www.jianshu.com/p/552df05d5a24