Java常用类库与技巧
一. Java异常体系
1.1 异常处理机制主要回答了哪些问题
- What:异常类型回答了什么被抛出
- Where: 异常栈堆跟踪回答了在哪抛出
- Why:异常信息回答了为什么被抛出
1.2 Java异常体系
- 图示
1.3 从概念角度解析Java的异常处理机制
- Error:程序无法处理的系统错误,编译器不做检查
- Exception:程序可以处理的异常,捕获后可能恢复
总结:前者是程序无法处理的错误,后者是可以处理的异常
RuntimeException: 不可预知的,程序应当自行避免; 非RuntimeException:可预知的,从编译器校验的异常
- 从责任角度看:
- Error属于JVM需要负担的责任
- RuntimeExcepiton是程序应该负担的责任;
- Checked Exception可检查异常是Java编译器应该负担的责任。
1.4 常见Error以及Exception
-
RuntimeException
- NullPointerException: 空指针引用异常
- ClassCastException: 类型强制转换异常
- IllegalArgumentException:传递非法参数异常
- IndexOutOfBoundsException:下标越界异常
- NumberFormatException:数字格式异常
-
非RuntimeException
- ClassCastNotFoundException: 找不到指定class异常
- IOException: IO操作异常
-
Error
- NoClassDefFoundError:找不到class定义的异常
- 可能的原因: ①类依赖的class或jar不存在 ②类文件存在,但是存在不同的域中 ③大小写问题,javac编译的时候是无视大小写的,很有可能编译出来的class文件就与想要的不一样
- StackOverflowError: 深递归导致栈被耗尽而抛出的异常
- OutOfMemoryError:内存溢出异常;
- NoClassDefFoundError:找不到class定义的异常
1.5 Java的异常处理机制
- 抛出异常:创建异常对象,交由运行时系统处理
- 捕获异常:寻找合适的异常处理器处理异常,否则终止运行;
1.6 Java异常的处理原则
- 具体明确:
- 抛出的异常应能通过异常类名和message准确说明异常的类型和产生异常的原因;
- 提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题;
- 延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常;
1.7 高效主流的异常处理框架
- 在用户看来,应用系统发生的所有异常都是应用系统内部的异常
- 设计一个通用的继承自RuntimeException的异常来统一处理
- 其余异常都统一转译为上述异常AppException
- 在catch之后,抛出上述异常的子类,并提供足以定位的信息
- 由前端接收AppException做统一处理;
1.8 Java异常处理消耗性能的地方
- try-catch块影响JVM的优化
- 异常对象实例需要保存栈快照等信息,开销较大
二. Java集合框架
2.1 工作中消失而面试却长存的算法与数据结构
- 优秀的算法和数据结构被封装到了Java的集合框架之中;
2.2 考点
-
数据结构考点
- 数组和链表的区别;
- 链表的操作,如反转,链表环路检测,双向链表,循环链表相关操作;
- 队列,栈的应用;
- 二叉树的遍历方式及其递归和非递归的实现;
- 红黑树的旋转;
-
算法考点:
- 内部排序:如递归排序、交换排序(冒泡、快排)、选择排序、插入排序;
- 外部排序:应掌握如何利用有限的内存配合海量的外部存储来处理超大的数据集,写不出来也要有相关思路;
-
考点扩展
- 哪些排序是不稳定的,稳定意味着什么
- 不同数据集,各种排序最好或最差的情况
- 如何优化算法
2.3 集合体系结构
- 图示
2.4 集合之List和Set
- 图示
2.5 集合之Map
- HashMap:[数组+链表+红黑树]
- 如何有效减少碰撞
- 扰动函数:促使元素位置分布均匀,减少碰撞几率
- 使用final对象,并采用合适的equals()和hashCode()方法
- 扩容问题
- 多线程环境下,调整大小会存在条件竞争,容易容易造成死锁;
- rehashing是一个比较耗时的过程;
- 链表与红黑树的相互转换:
- 超过8转为红黑树少于6则转为链表
- 如何有效减少碰撞
- HashMap知识点回顾:
- 成员变量:数据结构,树化阀值
- 构造函数:延迟创建
- put和get的流程
- 哈希算法,扩容,性能
2.6 如何让HashMap变成线程安全的?
public class SafeHashMapDemo{
public static void main(String[] args){
Map hashMap=new HashMap();
Map safeHashMap=Collections.synchronizedMap(HashMap); //通过此代码则会让其变成线程安全的,效果与HashTable一致,性能低下
safeHashMap.put("aa","1");
safeHashMap.put("bb","2");
System.out.println(safeHashMap.get("bb"));
}
}
2.7 如何优化Hashtable?
- 通过锁细粒度化,将整锁拆解成多个锁进行优化;
2.8 ConcurrentHashMap
- 早期:
- 分段锁:默认分成16个组,每个组都配一把锁,当一个组内的数据被锁的时候,不影响其他组内数据的读取;
- 结构:数组+链表
- 现在:
- 不允许插入null
- 分段锁
- CAS+synchronized使锁更细化
- 结构:数组+链表+红黑树
- put方法的逻辑
2.9 HashMap、Hashtable、ConccurentHashMap之间的区别?
- HashMap线程不安全,数组+链表+红黑树
- Hashtable线程安全,锁住整个对象,数组+链表
- ConccurentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
- HashMap的Key、value均可为null,而其他的两个类不支持;
三. J.U.C知识点梳理
3.1 java.util.concurrent:提供了并发编程的解决方案
- CAS是java.util.concurrent.atomic包的基础
- AQS是java.util.concurrent.locks包以及一些常用类比如Semophore,ReentrantLock等类的基础;
3.2 J.U.C包的分类
-
线程执行器executor
-
锁locks
-
原子变量类atomic
-
并发工具类tools
-
并发集合collections
-
结构图示:
-
并发工具类:
- 闭锁: CountDownLatch
- 栅栏: CyclicBarrier
- 信号量: Semaphore
- 交换器: Exchanger
3.3 常用
-
CountDownLatch:让主线程等待一组事件发生后继续执行
- 事件指的是CountDownLatch里的countDown()方法;
-
CyclicBarrier:阻塞当前线程,等待其他线程
- 等待其他线程,且会阻塞自己当前线程,所有线程必须同时到达栅栏位置后,才能继续执行;
- 所有线程到达栅栏处,可以出发执行另外一个预先设置的线程;
-
Semaphore
- 控制某个资源可被同时访问的线程个数
-
Exchange:
- 两个线程到达同步点后,互相交互数据;
-
代码示例:
public static void main(String[] args){
//代表男生和女生
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(()->{
try{
//男生对女生说的话
String girl=exchanger.exchange("我其实暗恋你很久了。。。");
System.out.println("女生说:"+girl);
}catch(InterruptedException e){
e.printStackTrace();
}
});
service.execute(()->{
try{
System.out.println("女生慢慢地从教室里走出来...");
TimeUnit.SECONDS.sleep(3);
//男生对女生说的话
String boy =exchanger.exchange("我很喜欢你...");
System.out.println("男生说"+boy);
}catch(InterruptedException e){
e.printStackTrace();
}
});
}
- BlockingQueue
- 主要用于生产者-消费者模式,在多线程场景时生产者线程在队列尾部添加元素,而消费者线程则在队列头部消费元素,通过这种方式能够达到将任务的生产和消费进行隔离的目的;
- 常用:
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列;
- LinkedBlockingQueue:一个由链表结构组成的有界/无界阻塞队列
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
- DealyQueue:一个使用优先级队列实现的无界阻塞队列
- SynchronousQueue:一个不存储元素的阻塞队列
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列;
有界的意思是容量是有限的,在初始化的时候我们必须指定它容量的大小,一旦指定就不可改变;无边界是有一个默认大小;支持优先级的那个值是必须要有可比性的;LinkedTransferQueue是5,2 的合体,它的性能比2更好;
四. Java的IO机制
4.1 BIO - Block-IO:InputStream和OutputStream,Reader和Writer
代码简单直观,但效率和扩展性存在瓶颈
4.2 NonBlock-IO:构建多路复用的、同步非阻塞的IO操作
提供更贴近底层的高性能操作方式,发起第一次请求的时候不会被阻塞,而是一直被查询是否准备好;
-
NIO的核心
- Channels
- FileChannel:
- transferTo()方法: 把FileChannel中的数据拷贝到另外一个Channel
- transferFrom:把另外一个Channel中的数据拷贝到FileChannel
- 避免了两次用户态和内核态间的上下文切换,即"零拷贝",效率较高
- DatagramChannel
- SocketChannel
- ServerSocketChannel
- FileChannel:
- Buffers
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
- Selectors
- 可以创建多个通道Channel
- Channels
-
消息传递方式:
- select: 内核需要将消息传递到用户空间,需要内核的拷贝动作
- poll:同上
- epoll:通过内核和用户空间共享一块内存来实现,性能较高
4.3 AIO- Asynchronous IO:基于事件和回调机制
-
图示:
-
AIO如何进一步加工处理结果?
- 基于回调: 实现CompletionHandler接口,调用时触发回调函数
- 返回Future:通过isDone()查看是否准备好,通过get()等待返回数据
4.4 BIO、NIO、AIO对比
- 图示:
BIO一个线程处理一个,NIO是一个server可以响应多个请求,BIO适用于连接数比较小且固定的架构,程序简单容易理解; NIO适用于连接数比较多且连接时间比较短的架构; AIO适用于连接数比较多且连接时间比较长的架构;