JavaIO流及NIO如何实现多路复用

初学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操作基于事件和回调,可以简单的理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成之后,操作系统会通知相应的线程进行后续工作。

面试中的方向通常是这样的:

  • java.io的基础包用法,像Input/OutStream,Reader/Writer
  • NIO和NIO2的基本组成
  • 给定场景,用不同模型实现,分析BIO和NIO等模式的设计和实现原理
  • NIO自身的问题,如何改进
  • NIO提供的高性能数据操作方式是基于什么原理,如何使用

知识扩展

同步和异步的区别
同步:有序运行,后续任务等待当前调用返回,才会执行下一步

异步:不需要等待当前调用返回,通常依赖事件、回调等机制实现任务间次序关系

阻塞与非阻塞的区别
阻塞:当阻塞的时候,当前线程会处于阻塞状态,无法从事其他任务,只有当任务就绪才能继续,比如ServerSocket新连接建立完毕,或者数据读取,写入操作完成

非阻塞:不管IO是否结束,直接返回,操作在后台自己处理。

不能仅凭字面理解,阻塞和同步就是低效,具体情况要看实际应用场景。

对于 java.io,大家都非常熟悉,但是还有很多底层和细节方面的问题需要仔细探究,以下是必须要了解到的知识点:

  • [1] IO不仅仅是对文件的操作,更包括了Socket。
  • [2]输入输出流是用于读取或写入字节的,例如操作图片文件。
  • [3]BufferedOutputStream这种带缓冲区的IO读写可以大大提升读写的效率,但是最后需要flush
  • [4]Reader/Writer 主要用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字符,不管是网络通信还是文件读取,Reader/Writer相当于构建了应用逻辑与原始数据之间的桥梁。
  • [5]很多IO工具类都实现了Closeable接口,因为要进行资源的释放。比如,打开FileInputStream,它就会获取相应的文件描述符(FileDescriptor),需要利用try-with-resource、try-finally等机制保证FileInputStream被明确关闭,进而响应的文件描述符也会失效,否则资源将无法释放。
    JavaIO流及NIO如何实现多路复用_第1张图片
    Java NIO概述
    NIO主要组成部分
    1、Buffer :高效的数据容器,除了布尔类型,所有原始数据类型都有响应的Buffer来实现
    2、Channel:批量操作IO的抽象
    3、Charset:提供Unicode字符串定义
    4、Selector:可以通过检测Channel的状态,达到单线程管理Channel

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);
	}

JavaIO流及NIO如何实现多路复用_第2张图片
当我们用普通IO进行这种低并发的时候,效果还是可以的,但是当连接数量急剧上升的时候,就会出现同步阻塞的低扩展劣势,而NIO解决了这样的问题。

1、首先,通过Selector.open()创建一个Selector,作为类似调度员的角色。
2、然后创建一个ServerSocketChannel,并且向Selector注册,通过指定SeletionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求。
注意,为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出异常
3、Selector阻塞在select操作,当有Channel发生接入请求,就会被唤醒。
4、在数据的方法中,通过SocketChannel和Buffer进行数据操作,在本例中是发送了一串String。

最终得出结论
IO都是同步阻塞,所以需要多线程,NIO则是单线程轮询,提高应用的扩展能力。
JavaIO流及NIO如何实现多路复用_第3张图片

你可能感兴趣的:(Java)