一、Java异常
1. 异常体系
RuntimeException:不可预知的异常,程序应当自行避免,是程序应该承担的责任
非RuntimeException:可预知的异常,从编译器可以校验的异常,是Java编译器应该承担的责任
2. Error和Exception的区别
Error:系统错误,程序无法处理的错误,Java编译器不做检查,属于JVM需要负担的责任,仅靠程序本身无法恢复和预防,如虚拟机崩溃,内存错误,空间不足,方法调用栈溢出等;
Exception:程序可以处理的异常,捕获后程序可以恢复运行;
总结:前者是程序无法处理的错误,后者是可以处理的异常
3. 常见Error和Exception
RuntimeException
-
NullPointerException - 空指针引用异常
-
ClassCastException - 类型强制转换异常
-
IllegalArgumentException - 传递非法参数异常
-
IndexOutOfBoundsException - 下标越界异常
-
NumberFormatException - 数字格式异常
非RuntimeException
-
ClassNoFoundException - 找不到指定class异常
-
IOException - IO操作异常
Error
-
NoClassDefFoundError - 找不到class定义的异常
-
StackOverflowError - 深递归导致栈被耗尽而抛出的异常
-
OutOfMemoryError - 内存溢出异常
4. 异常处理机制
原则:具体明确,提早抛出,延迟捕获。
技巧
1. 异常处理不能代替简单的测试;
2. 不要过分地细化异常;有必要将整个任务包装在一个 try 语句块;
3 . 利用异常层次结构;不要只抛出 RuntimeException 异常,应该寻找更加适当的子类或创建自己的异常类 。
4. 不要压制异常;
5. 在检测错误时,“苛刻 ” 要比放任更好;
6. 不要羞于传递异常;
二、集合
1. 数据结构考点
-
数组和链表的区别;
-
链表的操作,如反转、链表环路检测、双向链表、循环链表相关操作;
-
队列、栈的应用;
-
二叉树的遍历方式及其递归和非递归的实现;
-
红黑树的旋转;
2. 算法考点
-
内部排序:如递归排序、交换排序(冒泡、快排)、选择排序、插入排序;
-
外部排序:应掌握如何利用有限的内存配合海量的外部存储来处理超大的数据集,写不出来也要写出相关思路;
-
那些排序是不稳定的,稳定意味着什么;
-
不同数据集,各种排序的最好最差情况;
-
如何优化算法;
3. 集合框架
4. HashMap
1. HashMap(Java1.8以前):数组+链表
数组在没有赋初始值的长度默认是16,存储的是链表的头结点,性能O(1),最坏变成O(n);
Java1.8以后:数组+链表+红黑树,最坏的性能O(logn);
2. HashMap的put方法的逻辑:
如果HashMap未被初始化,则初始化;
对Key求Hash值,然后再计算下标;
如果没有碰撞,直接放入桶中;如果碰撞则以链表的形式存放在后面;
如果链表长度超过阈值8,就把链表转化为红黑树;如果链表长度低于6,则把链表转化为红黑树;
如果节点已经存在,则替换旧值;
如果桶满(容量16*加载因子0.75),就需要resize(扩容两倍后重排);
3. HashMap如何减少碰撞:
扰动函数:让元素位置分布均匀,减少碰撞;
使用final对象,并采用合适的equals和hashcode方法,例如用String和Integer这样类型的变量作为key;
4. HashMap扩容可能产生的问题:
多线程环境下,多个线程同时调整同一个HashMap会存在条件竞争,容易造成死锁;
rehashing是一个比较耗费时间的过程;
5. 线程安全
public class SafeHashMapDemo {
public static void main(String[] args) {
Map hashMap = new HashMap();
Map safeHashMap = Collections.synchronizedMap(hashMap);
safeHashMap.put("aaa","1");
safeHashMap.put("bbb","2");
System.out.println(safeHashMap.get("bbb"));
}
}
5. HashTable
1. 早期Java类库的哈希表的实现;
2. 线程安全:涉及到修改Hashtable的方法,都使用了synchronized去修饰;
3. 串行化的方式运行,性能较差,很少被推荐使用;
6. ConcurrentHashMap
早期ConcurrentHashMap:数组+链表,通过分段锁Segment来实现;
当前(java1.8)ConcurrentHashMap:数组+链表+红黑树,CAS+synchronized使锁更细化;
比起Segment,锁拆的更细:
首先使用无锁操作CAS插入头节点,失败则循环重试;
若头结点已存在,则尝试获取头结点的同步锁,在进行操作;
1. ConcurrentHashMap:put方法
1. 判断不允许插入NULL键;然后计算哈希值;
2. 判断Node[]数组是否为空,没有则进行初始化操作;
3. 通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下一次循环;
4. 检查到内部正在移动元素,就帮助他一起扩容;
5. 如果产生哈希碰撞,就会锁住链表或红黑树头节点,遍历节点,对链表或者红黑树进行添加元素操作;
6. 判断链表长度是否达到临界值8,是就将链表转化为红黑树,小于6则将红黑树转化为链表;
7.HashMap、Hashtable、ConcurrenHashMap三者区别
1. HashMap线程不安全,数组+链表+红黑树;
2. Hashtable线程安全,锁住整个对象,运行效率低,数组+链表;
3. ConcurrentHashMap线程安全,CAS+同步锁,数组+链表+红黑树;
4. HashMap的key、value均可以为null,其他两个不支持;
三、 J.U.C包
1. JUC开发包简介
1. java.util.concurrent开发包
传统线程编程模型之中为防止死锁等现象的出现(wait()、notify()、synchronized)时往往会考虑性能、公平性、资源管理等问题,这样加重了程序开发人员的负担;Java5.0添加了一个新的java.util.concurrent开发包(简称JUC)。利用此包进行的多线程编程将有效的减少竞争条件(race conditions)和死锁线程。
2. java.util.concurrent核心类
1. Executor:具有Runnable任务的执行者。
2. ExecutorService:一个线程池管理者,其实现类有多种,我们能把Runnable,Callable提交到池中让其调度。
3. Semaphore:信号量,一个计数信号量,控制某个资源可被同时访问的线程个数。
4. ReentrantLock:一个可重入的互斥锁定Lock,功能类似synchronized,但要强大的多。
5. Future:是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等,还提供了cancel终止线程。
6. BlockingQueue:阻塞队列,提供可阻塞的入队出队操作。
7. CompletionService:ExecutorService的扩展,可以获得线程执行结果的。
8. CountDownLatch:闭锁,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
9. CyclicBarrier:栅栏, 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。
10. Future:表示异步计算的结果。
11. ScheduldExecutorService:一个ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
四、IO机制
1. BIO、NIO、AIO
Block-IO(阻塞IO):基于字节流的InputStream和OutputStream,基于字符流的Read和Writer;交互方式是同步阻塞的方式,在读写操作完成之前,线程会一直阻塞。IO效率和扩展性存在瓶颈。
NonBolck-IO(非阻塞IO):构造多路复用、同步非阻塞的IO操作;程序要不断询问内核数据是否已经准备好;
NIO核心:Channels,Buffers,Selectors;
NIO底层:IO多路复用:调用系统级别的select、poll、epoll;单线程可以处理多个网络IO;
select、poll、epoll的区别:
单个进程的连接数
select | 单个进程所能打开的最大连接数由FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小是32*32,64位的机器上为32*64),我们可以对其进行修改,然后重新编译内核,但是性能无法保证,需要做进一步测试。 |
poll | 本质和select没有区别,但它没有最大连接数限制,基于链表存储。 |
epoll | 连接有上限,很大,1G内存的机器上可以打开10万的的连接。 |
FD剧增后带来的IO效率问题
select | 因为每次调用是会对连接进行线性遍历,所以随着FD的增加会造成遍历速度线性下降的性能问题。 |
poll | 同上 |
epoll | 由于epoll是根据每个FD上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll不会有线性下降的性能问题,但是所有socket都很活跃的情况下,可能有性能问题。 |
消息传递方式:
select | 内核需要将消息传递到用户空间,需要内核的拷贝动作 |
poll | 同上 |
epoll | 通过内核和用户空间共享一块内存来实现,性能较高。 |
Asynchronous IO:基于事件和回调机制。
AIO如何进一部加工处理结果:
基于回调:实现CompletionHandler接口,调用时触发回调函数;
返回Future:通过isDone()查看是否准备好,通过get()等待返回数据;
2. 对比
属性\模型 | 阻塞BIO | 非阻塞NIO | 异步AIO |
blocking | 阻塞并同步 | 非阻塞但同步 | 非阻塞并异步 |
线程数 | 1:1 | 1:N | 0:N |
复杂度 | 简单 | 较复杂 | 复杂 |
吞吐量 | 低 | 高 | 高 |