Java系列(四)| 异常处理及IO
Java 中异常处理体系?
Java 的异常体系是分为多层的。
Throwable
是 Java
语言中所有错误或异常的基类。
Throwable
Error
:系统内部错误比如虚拟机异常,是程序无法处理的。-
Exception
:程序问题导致的异常 CheckedException
受检异常:编译器会强制检查并要求处理的异常。RuntimeException
运行时异常:程序运行中出现异常,比如我们熟悉的空指针、数组下标越界等等
异常的处理方式?
针对异常的处理主要有两种方式:
- 遇到异常不进行具体处理,而是继续抛给调用者 (throw,throws)
抛出异常有三种形式,一是 throw
, 一个 throws
,还有一种系统自动抛异常。
throws
用在方法上,后面跟的是异常类,可以跟多个;而 throw
用在方法内,后面跟的是异常对象。
- try catch 捕获异常
在 catch
语句块中补货发生的异常,并进行处理。
try {
//包含可能会出现异常的代码以及声明异常的方法
}catch(Exception e) {
//捕获异常并进行处理
}finally { }
//可选,必执行的代码
}
try-catch
捕获异常的时候还可以选择加上 finally
语句块,finally
语句块不管程序是否正常执行,最终它都会必然执行。
三道经典异常处理代码题
题目 1
public class TryDemo {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
return 1;
} catch (Exception e) {
return 2;
} finally {
System.out.print("3");
}
}
}
执行结果:31。
try、catch。finally
的基础用法,在 return
前会先执行 finally
语句块,所以是先输出 finally
里的 3,再输出 return
的 1。
题目 2
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
try {
return 2;
} finally {
return 3;
}
}
}
执行结果:3。
try
返回前先执行 finally
,结果 finally
里不按套路出牌,直接 return
了,自然也就走不到 try
里面的 return
了。
finally
里面使用 return
仅存在于面试题中,实际开发这么写要挨吊的。
题目 3
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int i = 0;
try {
i = 2;
return i;
} finally {
i = 3;
}
}
}
执行结果:2。
大家可能会以为结果应该是 3,因为在 return
前会执行 finally
,而 i 在 finally
中被修改为 3 了,那最终返回 i 不是应该为 3 吗?
但其实,在执行 finally
之前,JVM
会先将 i 的结果暂存起来,然后 finally
执行完毕后,会返回之前暂存的结果,而不是返回 i,所以即使 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。
I/O
Java 中 IO 流分为几种?
流按照不同的特点,有很多种划分方式。
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流
Java Io
流共涉及 40 多个类,看上去杂乱,其实都存在一定的关联, Java I0
流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
IO 流用到了什么设计模式?
其实,Java 的 IO
流体系还用到了一个设计模式——装饰器模式。
InputStream
相关的部分类图如下,篇幅有限,装饰器模式就不展开说了。
既然有了字节流, 为什么还要有字符流?
其实字符流是由 Java
虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。
所以, I/O
流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
BIO、NIO、AIO?
BIO(blocking I/O)
:就是传统的 IO
,同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过连接池机制改善 (实现多个客户连接服务器)。
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,程序简单易理解。
NIO :全称 java non-blocking IO,
是指 JDK 提供的新 API。从 JDK1.4 开始,Java
提供了一系列改进的输入 / 输出的新特性,被统称为 NIO(即 New IO)。
NIO 是同步非阻塞的,服务器端用一个线程处理多个连接,客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有 IO
请求就进行处理:
NIO
的数据是面向缓冲区 Buffer 的,必须从 Buffer
中读取或写入。
所以完整的 NIO
示意图:
可以看出,NIO
的运行机制:
- 每个
Channel
对应一个Buffer
。 - Selector 对应一个线程,一个线程对应多个 s。
- Selector 会根据不同的事件,在各个通道上切换。
- Buffer 是内存块,底层是数据。
AIO
:JDK 7
引入了 Asynchronous I/O,
是异步不阻塞的 IO。在进行 I/O 编程中,常用到两种模式:Reactor
和 Proactor
。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理,完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
PS:关于同步阻塞 IO、同步不阻塞 IO、异步不阻塞 IO 的相关概念可以查看:面试字节,被操作系统问挂了