写在前面
🍁个人主页:HNUJSY
✨本期专栏:《史上最全经典面试题总结》欢迎订阅学习~
📌Xmind文件获取:GitHub 持续更新中,别忘了 star 喔~
「Java学习+面试指南」思维导图,计算机自学指南,包括Java基础、JVM、数据库、mysql、redis、计算机网络、算法、数据结构、操作系统等,后台技术栈/架构师之路/全栈开发社区,阿里,腾讯,百度,美团,头条等春招/秋招/校招/面试
思维导图(png格式可下载放大)
java集合
List
Arraylist
- 数组
- 查找、尾增删快
- 增加 50%
- 查找、尾增删快
LinkedList
- 双向循环链表
- 删除、插入快
Vector
- 线程安全
- Synchronized
- 扩容每次会增加 1 倍
- Synchronized
Collections.synchronizedList
CopyOnWriteArrayList
- 写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行
- 合适读多写少的场景
- 需要拷贝数组,会消耗内存
- 不能用于实时读的场景,像拷贝数组、新增元素都需要时间
- 没法保证 CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次 add/set 都要重新复制数组
- 加锁 + 数组拷贝+ volatile
- 1.加锁:保证同一时刻数组只能被一个线程操作;
- 2.数组拷贝:保证数组的内存地址被修改,修改后触发 volatile 的可见性,其它线程可以立马知道数组已经被修改;
- 3.volatile:值被修改后,其它线程能够立马感知最新值
- 1.volatile 关键字修饰的是数组,如果我们简单的在原来数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行。
- 2.在新的数组上进行拷贝,对老数组没有任何影响,只有新数组完全拷贝完成之后,外部才能访问到,降低了在赋值过程中,老数组数据变动的影响。
Set
TreeSet
- TreeMap
LinkedHashSet
- LinkedHashMap
HashSet
- HashMap
- HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT
set只能用迭代,因为他无序,无法用下标来取得想要的值
Map
TreeMap
- 有序
- 红黑树
- 对一个有序的key集合进行遍历
- 红黑树
- 在排序时如何比较元素
- 实现 Comparable 接口compareTo()方法
- Collections 工具类中的 sort()方法
- 第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),通过接口注入比较元素大小的算法
LinkedHashMap
- 双向链表
- 保持插入顺序
HashMap
- jdk7
- 数组+链表
- 扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算
- 头插法(先讲原位置的数据移到后1位,再插入数据到该位置),高并发可能会出现循环链表问题
- 扩容 hashCode ->> 扰动函数 ->> (h&length-1)
重新去计算其Hash值,根据Hash值对其进行分发
- 扩容 hashCode ->> 扰动函数 ->> (h&length-1)
- 头插法(先讲原位置的数据移到后1位,再插入数据到该位置),高并发可能会出现循环链表问题
- 扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算
- 数组+链表
- jdk8
- 大于阈值(默认为8),数组+红黑树,从原来的O(n)到O(logn)
- 扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
高16bit不变,低16bit和高16bit做了一个异或
两次就够了,已经达到了高位低位同时参与运算的目的- 尾插法(直接插入到链表尾部/红黑树)
- 扩容 扩容后的位置=原位置 or 原位置 + 旧容量
- 尾插法(直接插入到链表尾部/红黑树)
- 扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
- 大于阈值(默认为8),数组+红黑树,从原来的O(n)到O(logn)
- hashCode()与equals()
- 每个对象生成的hashcode通过hash分桶,再通过equals判断是否相同
- equals方法被覆盖过,则hashCode方法也必须被覆盖
- put
- hash 高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞
- 计算下标index = (table.length - 1) & hash
- null直接添加,不为空使用equals判断是否相同进行覆盖或者添加
- size是否超多了最大容量threshold,扩容或者红黑树
- null直接添加,不为空使用equals判断是否相同进行覆盖或者添加
- 计算下标index = (table.length - 1) & hash
- hash 高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞
- 扩容
- 初始大小为16,扩展2倍,Threshold 0.75
- 要么在原位置,要么移动到原偏移量两倍的位置
- HashMap 的长度为什么是2的幂次方
取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作
提高运算效率
&操作 1111取模
- HashMap 的长度为什么是2的幂次方
- 要么在原位置,要么移动到原偏移量两倍的位置
- 初始大小为16,扩展2倍,Threshold 0.75
HashTable
- synchronized 全表锁
- 初始大小为11,之后每次扩充,容量变为原来的2n+1
ConcurrentHashMap
- jdk7
- 分割分段(Segment),Segment + HashEntry,分段锁
- 默认分配16个Segment,比Hashtable效率提高16倍
- 分割分段(Segment),Segment + HashEntry,分段锁
- jdk8
- Node 数组+链表+红黑树
- synchronized 和 CAS 粒度为一个节点
- 只锁定当前链表或红黑二叉树的首节点
- Node还没有初始化,CAS插入
- Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁
- 只锁定当前链表或红黑二叉树的首节点
- synchronized 和 CAS 粒度为一个节点
- Node 数组+链表+红黑树
知识
fail-fast
- modCount
- 内容发生变化,就会改变modCount的值
- Iterator遍历的时候,检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出ConcurrentModificationException 异常,终止遍历。
集合不能被修改
- 使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合
移除 Collection
-
使用 Iterator.remove() 方法
Iterator it = list.iterator();
while(it.hasNext()){
// do something
it.remove();
}
for(Integer i : list){
list.remove(i)
}
会报 ConcurrentModificationException 异常。这是因为当使用 foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。
Iterator 和 ListIterator
- Iterator
- 遍历 Set 和 List 集合,只能单向遍历
- ListIterator
- 遍历 List,可以双向遍历
- 添加了一些额外的功能,添加一个元素、替换一个元素、获取前面或后面元素的索引位置
- 遍历 List,可以双向遍历
遍历方式
- for 循环遍历,基于计数器
- 迭代器遍历,Iterator
- foreach 循环遍历,foreach 循环遍历
- 只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。
ArrayList 的 elementData 加上 transient
private transient Object[] elementData;
-
不希望 elementData 数组被序列化,重写了 writeObject 实现
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}- 先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素
==与equals的区别
- ==
- 指向同一个内存空间,引用是否相同
- equals
- 所指向的内存空间的值是不是相同
BlockingQueue
- 用于实现生产者-消费者模式
- 在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间
- 如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
- add和offer
- 在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间
Array 和 ArrayList
- array 数组
- Arrays. asList(array)
- ArrayList 集合
- toArray()
Collection 和 Collections
- collection 集合类的一个顶级接口
- Collections 工具类/帮助类