仅作为面试知识点记录,具体课程请在慕课网搜索剑指Java面试-Offer直通车
Java异常
异常处理机制主要回答了三个问题
- What:异常类型回答了什么被抛出
- Where:异常堆栈跟踪回答了在哪抛出
- Why:异常信息回答了为什么被抛出
Error和Exception的区别
Java的异常体系
- RuntimeException:不可预知的,程序应当自行避免
- 非RuntimeException:可预知的,从编译器校验的异常
从概念角度解析Java的异常处理机制
- Error:程序无法处理的系统错误,编译器不做检查
- Exception:程序可以处理的异常,捕获后可能恢复
- 总结:前者是程序无法处理的错误,后者是可以处理的异常
从责任角度看
- Error属于JVM需要承担的责任
- RuntimeException是程序应该负担的责任
- Checked Exception可检查异常是Java编译器应该负担的责任
常见Error以及Exception
- RuntimeException
- NullPointerException - 空指针引用异常
- ClassCastException - 类型强制转换异常
- IllegalArgumentException - 传递非法参数异常
- IndexOutOfBoundsException - 下标越界异常
- NumberFormatException - 数字格式异常
- 非RuntimeException
- ClassNotFoundException - 找不到指定class的异常
- IOException - IO操作异常
- Error
- NoClassDefFoundError - 找不到class定义的异常
- StackOverflowError - 深递归导致栈被消耗尽而抛出的异常
- OutOfMemoryError - 内存溢出异常
Java的异常处理机制
- 抛出异常:创建异常对象,交由运行时系统处理
- 捕获异常:寻找合适的异常处理器处理异常,否则终止运行
Java异常的处理原则
- 具体明确:抛出的异常应能通过异常类名和message准确说明异常的类型和产生异常的原因;
- 提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题;
- 延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常
高效主流的异常处理框架
在用户看来,应用系统发生的所有异常都是应用系统内部的异常
- 设计一个通用的继承自RuntimeException的异常来统一处理
- 其余异常都统一转译为上述异常AppException
- 在catch之后,抛出上述异常的子类,并提供足以定位的信息
- 由前端接收AppException做统一处理
try-catch的性能
Java异常处理消耗性能的地方
- try-catch块影响JVM的优化
- 异常对象实例需要保存栈快照等信息,开销较大
Java集合框架
数据结构考点
- 数组和链表的区别;
- 链表的操作,如反转,链表环路检测,双向链表,循环链表相关操作;
- 队列,栈的应用;
- 二叉树的遍历方式及其递归和非递归的实现;
- 红黑树的选择;
算法考点
- 内部排序:如递归排序、交换排序(冒泡、快排)、选择排序、插入排序;
- 外部排序:应掌握如何利用有限的内存配合海量的外部存储来处理超大的数据集,写不出来也要有相关的思路
考点扩展
- 哪些排序是不稳定的,稳定意味着什么
- 不同数据集,各种排序最好或最差的情况
- 如何优化算法
集合之List和Set
集合之Map
HashMap、HashTable、ConcurrentHashMap
HashMap(Java8以前):数组+链表
HashMap(Java8及以后):数组+链表+红黑树
HashMap:put方法的逻辑
- 如果HashMap未被初始化过,则初始化
- 对Key求Hash值,然后再计算下标
- 如果没有碰撞,直接放入桶中
- 如果碰撞了,以链表的方式链接到后面
- 如果链表长度超过阈值,就把链表转成红黑树
- 如果链表长度低于6,就把红黑树转回链表
- 如果节点已经存在就替换旧值
- 如果桶满了(容量16*加载因子0.75),就需要resize(扩容2倍后重排)
HashMap:如何有效减少碰撞
- 扰动函数:促使元素位置分布均匀,减少碰撞几率
- 使用final对象,并采用合适的equals()和hashCode()方法
HashMap:从获取hash到散列的过程
HashMap:扩容的问题
- 多线程环境下,调整大小会存在条件竞争,容易造成死锁
- rehashing是一个比较耗时的过程
如何优化HashTable
- 通过锁细粒度化,将整锁拆解成多个锁进行优化
早期的ConcurrentHashMap:通过分段锁Segment来实现
当前的ConcurrentHashMap:CAS+synchronized使锁更细化
ConcurrentHashMap:put方法的逻辑
- 判断
Node[]
数组是否初始化,没有则进行初始化操作 - 通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进行下次循环
- 检查到内部正在扩容,就帮助它一块扩容
- 如果
f != null
,则使用synchronized锁住f元素(链表/红黑二叉树的头元素)
4.1 如果是Node(链表结构)则执行链表的添加操作
4.2 如果是TreeNode(树型结构)则执行树添加操作 - 判断链表长度已经达到临界值8,当然这个8是默认值,当节点数超过这个值就需要把链表转换为树结构
ConcurrentHashMap总结:比起Segment,锁拆得更细
- 首先使用无锁操作CAS插入头节点,失败则循环重试
- 若头节点已存在,则尝试获取头节点的同步锁,再进行操作
三者的区别
- HashMap线程不安全,数组+链表+红黑树
- HashTable线程安全,锁住整个对象,数组+链表
- ConcurrentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
- HashMap的key、value均可为null,而其他的两个类不支持