剑指Java面试-Java常用类库整理(不定期更新!)
文章目录
一、Java异常体系
异常处理机制主要回答了三个问题
- What:异常类型回答了什么被抛出
- Where:异常堆栈跟踪回答了在哪抛出
- Why:异常信息回答了为什么被抛出
1. Error和Exception的区别(Java异常体系)
Java异常体系:
- RuntimeException:不可预知的,程序应当自行避免
- 非RuntimeException:可预知的,从编译器校验的异常
从概念角度解析Java的异常处理机制:
- Error:程序无法处理的系统错误,编译器不做检查
- Exception:程序可以处理的异常,捕获后可能恢复
- 总结:前者是程序无法处理的错误,后者是可以处理的异常
从责任角度来看:
- Error属于JVM需要负担的责任
- RuntimeException是程序应该负担的责任
- Checked Exception可检查异常是Java编译器应该负担的责任
2. 常见Error以及Exception
RuntimeException:
- NullPointerException - 空指针引用异常
- ClassCastException - 类型强制转换异常
- IllegalArgumentException - 传递非法参数异常
- IndexOutOfBoundsException - 下标越界异常
- NumberFormatException - 数字格式异常
非RuntimeException:
- ClassNotFoundException - 找不到指定class的异常
- IOException - IO操作异常
Error:
- NoClassDefFoundError - 找不到class定义的异常
- StackOverflowError - 深递归导致栈被耗尽而抛出的异常
- OutOfMemoryError - 内存溢出异常
NoClassDefFoundError的成因:
- 类依赖的class或者jar不存在
- 类文件存在,但是存在不同的域中
- 大小写问题,javac编译的时候是无视大小写的,很有可能编译出来的class文件就与想要的不一样
3. Java的异常处理机制
- 抛出异常:创建异常对象,交由运行时系统处理
- 捕获异常:寻找合适的异常处理器处理异常,否则终止运行
接下来我们用代码说明一下:
public static int doWork(){
try {
int i = 10 / 0;
System.out.println("i = " + i);
}catch (ArithmeticException e){
System.out.println("ArithmeticException: " + e);
return 0;
}catch (Exception e){
System.out.println("Exception : " + e);
return 1;
}finally {
System.out.println("Finally");
return 2;
}
}
public static void main(String[] args) {
System.out.println("执行后的值为:" + doWork());
System.out.println("Mission Complete");
}
执行结果:
ArithmeticException: java.lang.ArithmeticException: / by zero
Finally
执行后的值为:2
Mission Complete
由执行结果,我们发现,代码进入了第一个catch块,打印了相关错误日志,然后在return之前进入了finally语句块,在finally语句块中遇到return,便提前返回了。
注意:
- 应尽量捕获具体异常,catch块只能进入一个。
- 应打印异常信息,不要捕获到异常不处理(应持久化日志中)
4. Java异常的处理原则
- 具体明确:抛出的异常应能通过异常类名和message准确说明异常的类型和产生异常的原因。
- 提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题
- 延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常
5. 高效主流的异常处理框架
在用户看来,应用系统发生的所有异常都是应用系统内部的异常
- 设计一个通用的继承RuntimeException的异常来统一处理
- 其余异常都统一转译为上述异常AppException
- 在catch之后,抛出上述异常的子类,并提供足以定位的信息
- 由前端接收AppException做统一处理
6. try-catch的性能
代码示例:
public class ExceptionPerformance {
public static void testException(String[] array){
try {
System.out.println(array[0]);
}catch (NullPointerException e){
System.out.println("array cannot be null");
}
}
public static void testIf(String[] array){
if (array != null){
System.out.println(array[0]);
}else {
System.out.println("array cannot be null");
}
}
public static void main(String[] args) {
long startException = System.nanoTime();
testException(null);
System.out.println("Exception cost" + (System.nanoTime() - startException));
long startIf = System.nanoTime();
testIf(null);
System.out.println("If cost" + (System.nanoTime() - startIf));
}
}
运行结果
array cannot be null
Exception cost394100
array cannot be null
If cost22000
由上述运行结果可以看到,try-catch要比if多消耗一个量级的时间。
Java异常处理消耗性能的地方:
- try-catch块影响JVM的优化
- 异常对象实例需要保存栈快照等信息,开销较大
二、Java集合框架
工作中消失而面试却长存的算法与数据结构,优秀的算法和数据结构被封装到了Java的集合框架中,只有我们了解了每一个集合框架中的底层数据结构与算法实现,我们才能够在特定的场景下挑选最适合的集合框架。
数据结构考点:
- 数组和链表的区别
- 链表的操作,如反转,链表环路检测,双向链表,循环链表相关操作
- 队列,栈的应用
- 二叉树的遍历方式及其递归和非递归的实现
- 红黑树的旋转
算法考点:
- 内部排序:如递归排序、交换排序(冒泡、快排)、选择排序、插入排序
- 外部排序:应掌握如何利用有限的内存配合海量的外部存储来处理超大的数据集,写不出来也要有相关的思路
考点扩展:
- 哪些排序是不稳定的,稳定意味着什么
- 不同数据集,各种排序最好或者最差的情况
- 如何优化算法
1. Java集合框架的整体设计
2. 集合之List和Set
3. 集合之Map
HashMap(Java8 以前):数组+链表
数组查询快增删慢,链表查询慢增删快,HashMap结合了俩者的优势,同时HashMap是非synchronized的,因此效率比较高,HashMap的键位组织情况大致如下图:
若键的hash冲突比较严重,则性能会严重恶化,从O(1)变成O(n)
HashMap(Java8 及以后):数组+链表+红黑树
Java8 及以后通过TREEIFY_THRESHOLD参数,来控制链表的长度,达到TREEIFY_THRESHOLD长度后将会由链表转换为红黑树。
这就将性能恶化后最坏的情况从O(n)提高到了O(logn)。
HashMap:put方法的逻辑
- 如果hashMap未被初始化过,则初始化
- 对Key求Hash值,然后再计算下标
- 如果没有碰撞,直接放入桶中
- 如果碰撞了,以链表的方式链接到后面
- 如果链表的长度超过阈值,就把链表转成红黑树
- 如果链表长度低于6,就把红黑树转回链表
- 如果节点已经存在就替换旧值
- 如果桶满了(容量16*加载因子0.75),就需要resize(扩容2倍后重排)
HashMap:如何有效减少碰撞
- 扰动函数:促使元素位置分布均匀,减少碰撞几率
- 使用final对象,并采用合适的equals()和hashCode()方法
HashMap中的hash()方法,从获取hash到散列的过程
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMap扩容的问题(rehashing)
- 多线程环境下,调整大小会存在条件竞争,容易造成死锁
- rehashing是一个比较耗时的过程
早期的ConcurrentHashMap
早期的ConcurrentHashMap:通过分段锁Segment来实现
数组+链表
concurrenthashmap01
Java8之后的ConcurrentHashMap
当前的CConcurrentHashMap:CAS + sysnchronized使锁更细化
数组+链表+红黑树
concurrenthashmap02
ConcurrentHashMap:put方法的逻辑
- 判断Node[]数组是否初始化,没有则进行初始化操作
- 通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下次循环
- 检查到内部正在扩容,就帮助它一块扩容。
- 如果看到头节点不为空。则使用synchronized锁住f元素(链表/红黑二叉树的头元素)
- 如果是Node(链表结构)则执行链表的添加操作
- 如果是TreeNode(树型结构)则执行树的添加操作
- 判断链表长度是否已经达到临界值9,当然这个8是默认值,可以自己去做调整,当节点数超过这个值就需要把链表转换为树结构
ConcurrentHashMap总结:
ConcurrentHashMap总结:比起Segment,锁拆分的更细一点
- 首先使用无锁操作CAS插入头节点,失败则循环重试
- 若头节点已经存在,则尝试获取头节点的同步锁,再进行操作
ConcurrentHashMap其他需要注意的点
- size()方法和mappingCount()方法的异同,俩者计算是否准确?
- 多线程环境下如何进行扩容
HashMap、Hashtable、ConcurrentHashMap三者的区别
- HashMap线程不安全,数组+链表+红黑树
- Hashtable线程安全,锁住整个对象,数组+链表
- ConcurrentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
- HashMap的key、value均可以为null,而其他俩个类不支持
三、JUC知识点梳理
java.util.concurrent:提供了并发编程的解决方案
- CAS是java.util.concurrent.atomic包的基础
- AQS是java.util.concurrent.locks包以及一些常用类比如Semophore\ReentrantLock等类的基础
1. JUC包的分类
- 线程执行器executor
- 锁locks
- 原子变量类atomic
- 并发工具类tools
- 并发集合collections
2. 并发工具类
- 闭锁 CountDownLatch
- 栅栏 CyclicBarrier
- 信号量 Semaphore
- 交换器 Exchanger
CountDownLatch
CountDownLatch:让主线程等待一组事件发生后继续执行
- 事件指的是CountDownLatch里的countDown()方法
CyclicBarrier
CyclicBarrier:阻塞当前线程,等待其他线程
- 等待其它线程,且会阻塞自己当前线程,所有线程必须同时到达栅栏位置后,才能继续执行
- 所有线程到达栅栏处,可以触发执行另外一个预先设置的线程
Semaphore
Semaphore:控制某个资源可被同时访问的线程个数
Exchanger
Exchanger:俩个线程到达同步点后,相互交换数据
3. 并发集合
BlockingQueue
BlockingQueue:提供可阻塞的入队和出队操作
主要用于生产者–消费者模式,在多线程场景时生产者线程在队列尾部添加元素,而消费者线程则在队列头部消费元素,通过这种方式能够达到将任务的生产和消费进行隔离的目的
BlockingQueue的实现有:
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:一个由链表结构组成的有界/无界阻塞队列
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
- DealyQueue:一个使用优先级队列实现的无界阻塞队列
- SynchronousQueue:一个不存储元素的阻塞队列
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
四、Java的IO机制
1. BIO(Block-IO)
Block-IO:InputStream 和OutputStream,Reader和Writer
2. NIO(NonBlock-IO)
NonBlock-IO:构建多路复用的同步非阻塞IO操作
NIO的核心
- Channels
- Buffers
- Selectors
NIO-Channels
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSockertChannel
其中FileChannel中的:
- transferTo:把FileChannel中的数据拷贝到另外一个Channel
- transferFrom:把另外一个Channel中的数据拷贝到FileChannel
避免了俩次用户态和内核态间的上下文切换,即“零拷贝”,效率较高。
NIO-Buffers
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
NIO-Selector
IO多路复用
select\poll\epoll的区别
支持一个进程所能打开的最大连接数
FD剧增后带来的IO效率问题
消息的传递方式
3. AIO(Asynchronous IO)
Asynchronous IO:基于事件和回调机制
AIO如何进一步加工处理结果
- 基于回调:实现CompletionHandler接口,调用时触发回调函数
- 返回Future:通过isDone()查看是否已经准备好,通过get()等待返回数据