JAVA NIO 系列- 01概述

一. NIO 、BIO 介绍

  1. BIO (blocking IO) , 即 阻塞型 IO,是JAVA 的传统 IO API , 是基于字节流 或字符流对数据进行操作而实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞卡在那里,它们之间的调用是可靠的线性顺序。优点就是代码比较简单直观, 缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。

  2. Java NIO(New IO / Non-blocking IO ) 就是 相对 BIO 提出的新的 JAVA IO API (New IO) ,是传统 IO API 的替代, 有时 NIO 也被称为非阻塞 IO .
    例如,一个线程通知 通道(channel )将数据读入缓冲区(buffer)。当通道(channel )将数据读入缓冲区(buffer)时,线程可以做其他事情,而不用阻塞等待.一旦数据被读入缓冲区,线程就可以继续处理它。将数据写入通道也是如此。

    2.1 NIO 可以构建多路复用的、同步非阻塞 IO 程序等,提升IO的吞吐量以及性能,NIO 是面相缓冲区的,即 读数据的时候先将数据从通道读取到缓冲区,写数据的时候要将数据从缓冲区写如到通道
    2.2 NIO 三大核心的概念:Channel(通道)、Buffer(缓冲区)、Selector(选择器).

IONIO
面向流(Stream oriented)面向缓冲区(Buffer oriented)
阻塞IO(Blocking IO)非阻塞IO(Non blocking IO)
选择器(Selectors)

二. 三大核心组件概念介绍

本小节先 对其概念进行一些解释,后续会有专门的章节来对每一个组件进行详细剖析.

上面我们说过, 标准 IO 是基于 字节流 和字符流, 而NIO 是基于 channel(通道) 和 buffers(缓存) 进行操作的, 数据总是从通道读取到缓冲区, 或者从缓冲区写入到通道 .

2.1 Channel

Java NIO 通道(Channel) 和 (Stream) 流 比较类似,但有一些区别:

  1. 可以对一个 Channel 进行读取 和写入, 但是 流(Streams) 通常是 单向的( 读 或者 写)
  2. Channel 可以异步的读写
  3. Channel 总是 读取到缓冲区 或者 从 缓冲区写入, Channel 必须对接的是 缓冲区(Buffer).

如上所述,将数据从通道读取到缓冲区,然后将数据从缓冲区写入通道。下面是一个例子:
Java NIO  Channel 读取数据到 Buffer,, 从Buffer 写入数据到 Channel

下面是 Channel 和 Stream 的区别

区别ChannelStream
是否支持异步支持不支持
是否支持双向传输支持 ,支持,既可以从通道读取数据,也可以向通道写入数据不支持
是否必须结合Buffer使用必须结合Buffer使用
性能较高较低

2.2 Buffer

Java NIO 缓冲区(Buffer) 是用来缓存数据的, 是用来和 Channel 打交道的,如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。

缓冲区本质上是一块内存,您可以将数据写入其中,然后再读取数据。这个内存块被包装成 一个 NIO Buffer 对象,该对象提供了一系列方法 使得更容易的操作内存块。

2.3 Selector

Java NIO 选择器(Selector) 是NIO中的一个重要组件,可以监视一个或多个通道中的事件(如:连接打开、数据到达等) ,检查 通道 Channel 实例 状态, 并确定哪些 通道Channel 已经准备好进行读取或者 写入操作,通过这种方式, 单个线程可以管理多个通道Channels , 从而管理多个网络连接.

为什么使用Selector?

仅仅使用一个线程来处理多个通道 Channel 的优势就是 : 只需要较少的线程来 处理 通道 Channel, 事实上, 只需要一个线程就可以处理所有的通道 Channels。 对于操作系统来说 ,在线程之间切换还是比较耗性能的,并且每一个线程也会占用操作系统的一些资源(内存), 所以 使用线程越水越好.

但是请记住,现代操作系统和CPU在多任务处理方面越来越好,因此多线程的开销随着时间的推移会越来越小。事实上,如果一个CPU有多个内核,那么不执行多任务可能会浪费CPU资源。无论如何,这个设计讨论属于不同的文本。这里只需说明,您可以使用选择器(Selector),用一个线程处理多个通道。

以下是使用选择器处理3个通道的线程的图示:
Java NIO : 一个线程 通过 Selector 操作3个 Channel

三、 demo

3.1 BIO 例子

这里的流程主要是如下:

  1. Server 端先绑定一个端口号,一直阻塞到有client 端来
  2. client 端连上Server 端
  3. 双方开始进行I/O 通信
  4. client 结束, 然后 server 端 等待下一个Client 的到来.
    在这里插入图片描述
    Server 端:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class IoServer {

    public static void main(String[] args) throws IOException {

        Socket socket = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            //创建serverSocket
            ServerSocket serverSocket = new ServerSocket(8888);
            // 服务器会一直开启,等待client来连接
            while (true) {
                //监听等待客户端连接,这里会一直阻塞,直到到Client 连接
                System.out.println("我在这里一直等待阻塞,直到有 Client 的连接");
                socket = serverSocket.accept();
                System.out.println("终于有一个Client 连接了,不然一直运行不到我这里啊");
                // 获取Client端的数据
                inputStream = socket.getInputStream();
                // 回应Client端的数据
                outputStream = socket.getOutputStream();

                byte[] bytes = new byte[1024];
                int readLen;
                while (true) {
                    if ((readLen = inputStream.read(bytes)) != -1) {

                        String msg = new String(bytes, 0, readLen);
                        System.out.println(" client[Data] :" + msg);

                        //这里加了一个换行符,是由于用的 reader.readLine()读取的
                        outputStream.write((msg + "\n").getBytes());
                        outputStream.flush();
                    } else {
                        break;
                    }
                }
                System.out.println("Client 断开了,不然会一直阻塞在上面的 inputStream.read ");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
            if (socket != null) {
                socket.close();
            }
        }
    }
}

client 端:


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

public class IoClient {

    public static void main(String[] args) throws IOException {

        Socket socket = new Socket("127.0.0.1", 8888);

        Scanner scanner = new Scanner( System.in);

        InputStream inputStream = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

        //获取输出字符流
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
        while (true) {

            String msg = scanner.nextLine();
            writer.write(msg);
            writer.flush();
            // 读

            String str = reader.readLine();
            System.out.println("server:" + str);

            if ("BYE".equalsIgnoreCase(msg)) {
                break;
            }
        }
        socket.close();
    }
}

3.2 NIO 例子

server端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NioServer {

    public static void main(String[] args) throws IOException, InterruptedException {
        //定义一个通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置为非阻塞方式
        serverSocketChannel.configureBlocking(false);
        //绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(9000));

        //设置一个 缓冲区,channel 必须对接Buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        while (true) {
            // 这里不会阻塞,如果有连接上,就返回channel,下面的代码可以继续运行,没有就返回null
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel != null) {
                System.out.println("=Client端 连接了");
                // 读取
                int len;
                try {
                    while ((len = socketChannel.read(byteBuffer)) > 0) {

                        //读取数据
                        byteBuffer.flip();
                        byte[] bytes = new byte[byteBuffer.remaining()];
                        byteBuffer.get(bytes);
                        String msg = new String(bytes).trim();
                        System.out.println("Client:" + msg);

                        //返回给client
                        byteBuffer.flip();
                        socketChannel.write(byteBuffer);
                        byteBuffer.clear();
                    }
                } catch (Exception e) {

                }
            } else {
                System.out.println("还没有Client端 连接");
                Thread.sleep(1000L);
            }
        }
    }
}

client端:


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class NioClient {

    public static void main(String[] args) throws IOException, InterruptedException {

        //定义一个通道
        final SocketChannel channel = SocketChannel.open();
        //设置非阻塞方式
        channel.configureBlocking(false);
        //设置服务端的ip和端口号
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9000);

        //连接服务器端, 这里配置的是 非阻塞,所以会立刻返回,如果是false ,后续还需要调用finishConnect() 进一步判断.
        if (!channel.connect(address)) {
            //当连接服务端时,客户端不会处于阻塞状态,还需要继续执行下面的代码
            while (!channel.finishConnect()) {
                System.out.println("还在尝试连接中. . . ");
                TimeUnit.MILLISECONDS.sleep(10);
            }
        }

        //设置一个 缓冲区,写入 channel 必须对接Buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        Scanner scanner = new Scanner(System.in);

        //设置一个获取buffer
        ByteBuffer readByteBuffer = ByteBuffer.allocate(1024);

        // 发送数据 非阻塞模式下

        while (true) {

            //写给server
            String msg = scanner.nextLine();
            byteBuffer.put(msg.getBytes());
            byteBuffer.flip();
            channel.write(byteBuffer);
            byteBuffer.clear();

            // 读取从server 传过来的数据
            int len = -1;
            while ((len = channel.read(readByteBuffer)) > 0) {
                readByteBuffer.flip();
                System.out.println("server:" + new String(readByteBuffer.array(), 0, len));
                readByteBuffer.clear();
            }

            if ("bye".equalsIgnoreCase(msg)) {
                break;
            }
        }
        //关闭
        channel.close();
    }
}

这里的 serverSocketChannel.accept(); 没有阻塞,会继续运行下面的代码, 这里涉及 Buffer,Channel,等相关概念,在后续的章节详细描述, 这里仅给出一个例子.

四、 小结

本章主要是简单介绍了一下 NIO和IO 相关的概念, 并大体上解释了一下NIO的三大组件, 后续会对每一个组件详细介绍.

附:NIO 这块 内容主要是来着< JAVA NIO>书籍 和 JAVA-NIO 网站翻译

支付宝微信
支付宝微信
如果有帮助记得打赏哦特别需要您的打赏哦
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直打铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值