初学Java的时候大家都会接触到各种各样的IO流,IO流的扩展方式是多种多样的,并且流的相关知识也是非常重要的,在面试和实际开发时候用的也是很多的,现在主要来介绍一下IO流。
Java IO流方式多种多样,可以从IO抽象模型和交互方式,进行简单的划分。
第一,传统的java.io包,完全基于流模型实现,提供了一些我们熟知的IO功能,比如File抽象、输入输出流等等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流的时候,在这两个操作结束之前线程呈阻塞状态,但是呈线性所以有一定规律。
java.io包的特点是,代码简单,原理移动,可扩展性低,性能差。
第二,为了在保证原本功能的情况下提升性能,所以java1.4引入了NIO框架,提供了Channel,Selector,Buffer等新的抽象类,构建多路复用,同步且非阻塞IO程序,且提高了性能。
第三,在java 7中,NIO又进行了更新,也就是NIO2,引入了异步非阻塞IO,也就是我们听过的AIO(Asynchronous IO) 。异步IO操作基于事件和回调,可以简单的理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成之后,操作系统会通知相应的线程进行后续工作。
面试中的方向通常是这样的:
同步和异步的区别
同步:有序运行,后续任务等待当前调用返回,才会执行下一步
异步:不需要等待当前调用返回,通常依赖事件、回调等机制实现任务间次序关系
阻塞与非阻塞的区别
阻塞:当阻塞的时候,当前线程会处于阻塞状态,无法从事其他任务,只有当任务就绪才能继续,比如ServerSocket新连接建立完毕,或者数据读取,写入操作完成
非阻塞:不管IO是否结束,直接返回,操作在后台自己处理。
不能仅凭字面理解,阻塞和同步就是低效,具体情况要看实际应用场景。
对于 java.io,大家都非常熟悉,但是还有很多底层和细节方面的问题需要仔细探究,以下是必须要了解到的知识点:
NIO应用场景
实现一个服务器应用,只简单要求能够同时服务多个客户端请求即可。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class NIO extends Thread{
private ServerSocket serverSocket;
public int getPort() {
return serverSocket.getLocalPort();
}
public void run() {
try {
serverSocket = new ServerSocket(0);
while (true) {
Socket socket = serverSocket.accept();
RequestHandler requestHandler = new RequestHandler(socket);
requestHandler.start();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
NIO nIO = new NIO();
nIO.start();
try(Socket client = new Socket(InetAddress.getLocalHost(), nIO.getPort())){
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println(s));
}
}
}
//简化实现
class RequestHandler extends Thread {
private Socket socket;
RequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (PrintWriter out = new PrintWriter(socket.getOutputStream())) {
out.print("Hello world");
out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
实现要点是:
服务器端启动ServerSocket,端口0表示总动绑定一个空闲端口
调用accept方法,阻塞等待客户端连接
利用Socket模拟了一个简单的客户端,只连接、读取、打印
当连接建立之后,启动一个单线程负责恢复客户端请求
存在的潜在问题:
java的线程是比较重量级的,所以启动、关闭、销毁一个进程是有一定开销的,每个线程都有单独的线程栈,需要很明显的内存支持,因此可以引入线程池。
serverSocket = new ServerSocket(0);
executor = Executors.newFixedThreadPool(8);
while (true) {
Socket socket = serverSocket.accept();
RequesHandler requesHandler = new RequesHandler(socket);
executor.execute(requesHandler);
}
当我们用普通IO进行这种低并发的时候,效果还是可以的,但是当连接数量急剧上升的时候,就会出现同步阻塞的低扩展劣势,而NIO解决了这样的问题。
1、首先,通过Selector.open()创建一个Selector,作为类似调度员的角色。
2、然后创建一个ServerSocketChannel,并且向Selector注册,通过指定SeletionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求。
注意,为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出异常
3、Selector阻塞在select操作,当有Channel发生接入请求,就会被唤醒。
4、在数据的方法中,通过SocketChannel和Buffer进行数据操作,在本例中是发送了一串String。