Java -- 什么是NIO

Java – 什么是NIO

1. Java NIO 基本介绍

  1. Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的。
  2. NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。
  3. NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
  4. NIO 是 面向缓冲区 ,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
  5. Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

2. NIO 和 BIO 的比较

  1. BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。
  2. BIO 是阻塞的,NIO 则是非阻塞的。
  3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

3. NIO 三大核心示意图

  1. 每一个Channel都会对应一个Buffer,并且Channel是双向的。
  2. Selector对应一个线程,一个线程对应多个Channel。
  3. Selector 会根据不同的事件,在各个通道上切换。
  4. 数据的读取写入是通过 Buffer,Buffer 就是一个内存块 ,底层是有一个数组。

4. 缓冲区Buffer

4.1 基本介绍

缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer完成。

4.2 Buffer常用类

在NIO中,Buffer是一个抽象的父类,一般我们使用其子类来完成读写操作,其子类有:

  • ByteBuffer:存储字节数据到缓冲区
  • ShortBuffer:存储字符串数据到缓冲区
  • CharBuffer:存储字符数据到缓冲区
  • IntBuffer:存储整数数据到缓冲区
  • LongBuffer:存储长整数数据到缓冲区
  • DoubleBuffer:存储小数数据到缓冲区
  • FloatBuffer:存储小鼠数据到缓冲区
4.3 Buffer四大属性
属性描述
Capacity容量,即可以容纳的最大数据量。在缓冲区创建时被指定,不能更改。
Limit相当于一个索引,只能对索引之前的数据进行读写操作,之后的数据无法操作。
Position相当于一个索引,表示从该位置起进行读写操作,并往后移,但不能超过Limit。
Mark标记,一般无需关注。

5. 通道Channel

5.1 基本介绍

1)NIO的通道类似于流,但与流有所区别:

  • 通道可以同时进行读写,而流只能读或者只能写
  • 通道可以实现异步读写数据
  • 通道可以从缓冲读数据,也可以写数据到缓冲

2)BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。

3)Channel 在 NIO 中是一个接口

public interface Channel extends Closeable{}
5.2 常用的Channel类有:
  • FileChannel:用于文件的数据读写。
  • DatagramChannel :用于UDP的数据读写。
  • ServerSocketChannel :用于TCP的数据读写。
  • SocketChannel: 用于TCP的数据读写。

ServerSocketChannel 和 SocketChannel类似于 ServerSocket 和 Socket的关系。

6. Selector选择器

6.1 基本介绍
  • Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)。
  • Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
  • 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
  • 避免了多线程之间的上下文切换导致的开销。
6.2 Selector类相关方法
public static Selector open(); // 得到一个选择器对象
public int select(long timeout); // 监控所有注册的通道,当其中有IO操作可以进行时,将对应的SelectionKey加入到内部集合中并发回,参数用来设置超时时间。
public Set<SelectionKey> selectedKeys(); // 从内部集合中得到所有的SelectionKey

7. NIO 非阻塞 原理和步骤

  1. 当客户端连接时,会通过 ServerSocketChannel 得到 SocketChannel。
  2. Selector进行监听select方法,返回有事件发生的通道的个数。
  3. 将SocketChannel注册到Selector上,一个Selector可以注册多个SocketChannel。
  4. 注册后返回一个 SelectionKey, 会和该 Selector 关联(集合)。
  5. 进一步得到各个 SelectionKey (有事件发生)。
  6. 在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()。
  7. 可以通过得到的 channel , 完成业务处理。

服务端代码:

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws Exception {
        //创建 ServerSocketChannel -> ServerSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //得到一个 Selecor 对象
        Selector selector = Selector.open();
        //绑定一个端口 6666, 在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //把 serverSocketChannel 注册到 selector 关心 事件为 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循环等待客户端连接
        while (true) {
            //这里我们等待 1 秒,如果没有事件发生, 返回
            if (selector.select(1000) == 0) { //没有事件发生
                System.out.println("服务器等待了 1 秒,无连接");
                continue;
            }
            //如果返回的>0, 就获取到相关的 selectionKey 集合
            //1.如果返回的>0, 表示已经获取到关注的事件
            //2. selector.selectedKeys() 返回关注事件的集合
            // 通过 selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //遍历 Set<SelectionKey>, 使用迭代器遍历
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()) {
                //获取到 SelectionKey
                SelectionKey key = keyIterator.next();
                //根据 key 对应的通道发生的事件做相应处理
                if (key.isAcceptable()) { //如果是 OP_ACCEPT, 有新的客户端连接
                    //该该客户端生成一个 SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println(" 客 户 端 连 接 成 功 生 成 了 一 个 socketChannel " +
                            socketChannel.hashCode());
                    //将 SocketChannel 设置为非阻塞
                    socketChannel.configureBlocking(false);
                    //将 socketChannel 注册到 selector, 关注事件为 OP_READ, 同时给 socketChannel关联一个 Buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (key.isReadable()) { //发生 OP_READ
                    //通过 key 反向获取到对应 channel
                    SocketChannel channel = (SocketChannel) key.channel();
                    //获取到该 channel 关联的 buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    channel.read(buffer);
                    System.out.println("form 客户端 " + new String(buffer.array()));
                }
                //手动从集合中移动当前的 selectionKey, 防止重复操作
                keyIterator.remove();
            }
        }
    }
}

客户端代码:

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOClient {
    public static void main(String[] args) throws Exception{
        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //提供服务器端的 ip 和 端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        //连接服务器
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作..");
            }
        }
        //...如果连接成功,就发送数据
        String str = "hello, 尚硅谷~";
        //Wraps a byte array into a buffer
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        //发送数据,将 buffer 数据写入 channel
        socketChannel.write(buffer);
        System.in.read();
    }
}

8. 使用NIO实现多人聊天群

要求:实现多人聊天,即一个客户端发送消息,其它连接服务端的客户端能够收到消息。

服务端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

public class GroupChatServer {

    // 选择器
    private Selector selector;
    // 通道
    private ServerSocketChannel serverSocketChannel;
    // 端口
    private static final int PORT = 3000;

    // 构造器, 初始化
    public GroupChatServer() {
        try {
            // 得到选择器
            selector = Selector.open();
            // 得到通道
            serverSocketChannel = ServerSocketChannel.open();
            // 绑定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            // 设置非阻塞模式
            serverSocketChannel.configureBlocking(false);
            // 将通道注册到选择器上
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 监听
    public void listen() {
        // 循环
        while (true) {
            try {
                int count = selector.select(1_000);
                // 有事件处理
                if (count > 0) {
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        // 去出SelectionKey
                        SelectionKey key = iterator.next();
                        // 监听到客户端请求连接
                        if (key.isAcceptable()) {
                            SocketChannel socketChannel = serverSocketChannel.accept();
                            socketChannel.configureBlocking(false); // 设为非阻塞
                            // 将通道注册到选择器
                            socketChannel.register(selector, SelectionKey.OP_READ);
                            System.out.println(socketChannel.getRemoteAddress() + "上线");
                        } else if (key.isReadable()) { // 通道发送 read 事件,即通道是可读的状态
                            // 处理读消息
                            readData(key);
                        }
                        // 删除当前SelectionKey, 防止重读处理
                        iterator.remove();
                    }
                } else {
                    System.out.println("等待中----");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    // 处理客户端发来的消息
    private void readData(SelectionKey key) {
        // 获取到关联的通道
        SocketChannel socketChannel = (SocketChannel) key.channel();
        // 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        try {
            // 将通道里的数据写入到buffer当中
            int read = socketChannel.read(byteBuffer);

            if (read > 0) {
                // 把缓存区的数据转成字符串
                String msg = new String(byteBuffer.array());
                // 打印消息
                System.out.println("客户端发送消息:" + msg);
                // 向其他的客户端转发消息
                sendInfoToOtherClients(msg, socketChannel);
            }

        } catch (IOException e) {
            // 取消注册
            key.cancel();
            // 关闭通道
            try {
                socketChannel.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
            e.printStackTrace();
        }
    }

    // 转发消息给其它客户(通道)
    private void sendInfoToOtherClients(String msg, SocketChannel self) {
        // 遍历 所有注册到 selector 上的 SocketChannel,并排除 自身通道
        for (SelectionKey key : selector.keys()) {
            // 通过 key 取出对应的 SocketChannel
            Channel targetChannel = key.channel();

            // 排除自己
            if (targetChannel instanceof SocketChannel && targetChannel != self) {
                // 转型
                SocketChannel dest = (SocketChannel) targetChannel;
                // 将 msg 存储到 buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                // 将 buffer 的数据写入 通道
                try {
                    dest.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        // 创建服务器对象
        GroupChatServer chatServer = new GroupChatServer();
        // 启动监听
        chatServer.listen();
    }
}

客户端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;

public class GroupChatClient {
    // 定义相关的属性
    private final String HOST = "127.0.0.1"; // 服务器的 ip
    private final int PORT = 3000; // 服务器端口
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    // 构造器, 完成初始化工作
    public GroupChatClient() {
        try {
            selector = Selector.open();
            // 连接服务器
            socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
            // 设置非阻塞
            socketChannel.configureBlocking(false);
            // 将 channel 注册到 selector
            socketChannel.register(selector, SelectionKey.OP_READ);
            // 得到 username
            username = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println(username + " is ok...");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    // 向服务器发送消息
    public void sendInfo(String info) {
        info = username + " 说:" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 读取从服务器端回复的消息
    public void readInfo() {
        try {
            int readChannels = selector.select();
            if (readChannels > 0) {// 有可以用的通道
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if (key.isReadable()) {
                        // 得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        // 得到一个 Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        // 读取
                        sc.read(buffer);
                        // 把读到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
                iterator.remove(); // 删除当前的 selectionKey, 防止重复操作
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        // 启动我们客户端
        GroupChatClient chatClient = new GroupChatClient();
        // 启动一个线程, 每个 3 秒,读取从服务器发送数据
        new Thread(() -> {
            while (true) {
                chatClient.readInfo();
                try {
                    Thread.currentThread().sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        // 发送数据给服务器端
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String s = scanner.nextLine();
            chatClient.sendInfo(s);
        }
    }
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: nio-multipart 是一个用于处理 Multipart 请求的依赖。Multipart 请求是一种 HTTP 请求格式,允许在单个请求中传输多种类型的数据,包括文本、文件、图像等。nio-multipart 提供了处理这种请求格式的工具和功能。 使用 nio-multipart,开发人员可以轻松地解析和处理 Multipart 请求。它提供了一种简单而灵活的方式,可以从请求中提取不同的数据部分,并以可读的格式进行操作。无论是处理上传的文件、解析数据字段还是获取请求的属性,nio-multipart 都提供了便捷的方法。 该依赖还支持文件上传,并提供了一套完整的 API 来处理上传的文件。使用 nio-multipart,开发人员可以轻松地接收上传的文件,并对其进行验证、存储和处理。它还支持上传文件的大小限制、文件类型验证等常见的文件处理需求。 nio-multipart 还提供了处理编码类型的支持。开发人员可以使用 nio-multipart 来处理不同的编码类型,例如表单数据的 URL 编码或二进制数据的 base64 编码。它还提供了编码类型之间的转换,以便开发人员可以方便地在不同的编码类型之间进行转换。 总而言之,nio-multipart 是一个用于处理 Multipart 请求的依赖,它提供了解析、处理和操作 Multipart 请求的功能,包括数据字段的提取、文件上传的支持和编码类型的处理等。使用 nio-multipart,开发人员可以更加便捷地处理复杂的请求格式,提高开发效率。 ### 回答2: NIO-Multipart是一个Java库工具,它提供了一种使用非阻塞I/O(NIO)实现的提供文件上传和处理多部分表单数据的方法。在Web应用程序开发中,常常需要处理用户提交的文件和表单数据,而NIO-Multipart可以帮助开发人员轻松地实现这些功能。 具体来说,NIO-Multipart提供了以下功能和依赖: 1. 文件上传:NIO-Multipart可以将客户端上载的文件保存到服务器的指定位置。它提供了一种简便的方法来访问上传的文件和相关的表单数据。 2. 处理多部分表单数据:NIO-Multipart可以解析提交的多部分表单数据,包括普通的表单字段和文件字段。它可以将这些数据提取出来,并以易于使用的方式提交给开发人员进行处理。 3. 依赖:NIO-Multipart依赖于JavaNIO库,使用其提供的非阻塞I/O(NIO)功能来处理文件上传和表单数据。 使用NIO-Multipart库,开发人员可以轻松处理和管理文件上传和多部分表单数据。通过利用非阻塞I/O(NIO)的优势,可以实现高效的文件传输和数据处理,提升了Web应用程序的性能和用户体验。 总之,NIO-Multipart是一种用于处理文件上传和多部分表单数据的Java库工具,它依赖于JavaNIO库,提供了一种使用非阻塞I/O实现的方法。 ### 回答3: nio-multipart是Java中的一个依赖库,用于处理基于NIO的多部分请求(multipart requests)。 首先,多部分请求是一种HTTP请求类型,允许在单个请求中传输多个文件和文本数据。它通常用于文件上传功能,在Web应用程序中非常常见。 nio-multipart提供了一种简单和高效的方式来解析和处理多部分请求。它基于JavaNIO(New I/O)库开发,该库提供了更快和更强大的I/O操作功能。 使用nio-multipart,我们可以轻松地从多部分请求中提取文件和文本数据,并对它们进行处理。它提供了易于使用的API,用于读取和解析请求的各个部分。我们可以访问每个部分的内容、类型、大小等信息,以便根据需要进行处理。 另外,nio-multipart还可以处理大文件上传,并在处理过程中节省内存。它使用NIO的特性,将请求数据分成一系列块,逐个处理而不是将整个文件加载到内存中。这种方式可以减少内存使用量,并提高性能和吞吐量。 综上所述,nio-multipart是一个用于处理基于NIO的多部分请求的依赖库。它简化了多部分请求的解析和处理过程,并提供了高效处理大文件上传的方法。它可以在Web应用程序中广泛使用,提高开发效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值