第11讲 | Java提供了哪些IO方式? NIO如何实现多路复用?

Java IO 方式有很多种,基于不同的 IO 抽象模型和交互方式,可以进行简单区分。

  • 同步、阻塞的方式(BIO): 传统的 java.io 包,它基于流模型实现,比如 File 抽象、输入输出流等.在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里。java.io 包的好处是代码比较简单、直观,缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。

人们也把 java.net 下面提供的部分网络 API,比如Socket、ServerSocket、HttpURLConnection 也归类到同步阻塞 IO ,因为网络通信同样是 IO 行为。

  • Java1.4引入NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层的高性能数据操作方式。

  • Java7引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO/NIO2)。异步 IO 操作基于事件和回调机制。

区分同步或异步(synchronous/asynchronous)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步。;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。

阻塞与非阻塞(blocking/non-blocking)。在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 ServerSocket 新连接建立完毕,或数据读取、写入操作完成;而非阻塞则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理。

  • IO 不仅仅是对文件的操作,网络编程中,比如Socket 通信,都是典型的 IO 操作目标。
  • 输入流、输出流(InputStream/OutputStream)是用于读取或写入字节的,例如操作图片文件。
  • Reader/Writer 则是用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer 相当于构建了应用逻辑和原始数据之间的桥梁。
  • BufferedOutputStream 等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高 IO 处理效率。这种设计利用了缓冲区,将批量数据进行一次操作,但在使用中千万别忘记flush。
  • 很多 IO 工具类都实现了 Closeable 接口,因为需要进行资源的释放。打开 FileInputStream,它就会获取相应的文件描述符(FileDescriptor),需要利用 try-with-resource,try-finally等机制保证 FileInputStream 被明确关闭,进而相应文件描述符也会失效,否则将导致资源无法被释放。
image.png

NIO 的主要组成部分

  • Buffer,高效的数据容器,除了布尔类型,所有原始数据类型相应的Buffer.

  • Channel,类似在 Linux 之类操作系统上看到的文件描述符,是 NIO 中被用来支持批量式 IO 操作的一种抽象。
    File 或者 Socket,通常被认为是比较高层次的抽象,而 Channel 则是更加操作系统底层的一种抽象,这也使得 NIO 得以充分利用现代操作系统底层机制,获得特定场景的性能优化。不同层次的抽象是相互关联的,我们可以通过 Socket 获取channel,反之亦然。

  • Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在 Selector 上的多个channel,是否有channel处于就绪状态,进而实现了单线程对多 Channel 的高效管理。

  • Selector 同样是基于底层操作系统机制,不同模式、不同版本都存在区别。linux中依赖于epoll。

NIO 能解决什么问题?

设想,我们需要实现一个服务器应用,只简单要求能够同时服务多个客户端请求即可。

使用 java.io 和 java.net 中的同步、阻塞式API可以简单实现:

public class DemoServer extends Thread {
    private ServerSocket serverSocket;
    public int getPort() {
        return  serverSocket.getLocalPort();
    }
    public void run() {
        try {
            // 绑定端口0
            serverSocket = new ServerSocket(0);
            while (true) {
                // 阻塞等待客户端连接
                Socket socket = serverSocket.accept();
                RequestHandler requestHandler = new RequestHandler(socket);
                requestHandler.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                ;
            }
        }
    }
    public static void main(String[] args) throws IOException {
        DemoServer server = new DemoServer();
        server.start();
        // 模拟了一个简单的客户端,只进行连接、读取、打印
        try (Socket client = new Socket(InetAddress.getLocalHost(), server.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.println("Hello world!");
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 }

你可能感兴趣的:(第11讲 | Java提供了哪些IO方式? NIO如何实现多路复用?)