前言

高级Java课程五一布置的小实验有一个是使用 NIO 完成一个群聊服务端和客户端,故学习学习。

NIO全称为 Non-Blocking IO,是一种同步非阻塞IO模型,也是IO多路复用的基础,迄今已经成为了解决高并发大流量的有效IO解决方案。

在之前传统的阻塞式编程的时候,我们常常使用serverSocket.accept() 等阻塞式操作来获取客户端的Socket连接,不过这个传统模式严重依赖于线程,但我们知道线程的创建和销毁都是重量级的系统函数。

传统阻塞式模型的缺点:

  1. 线程的创建和销毁成本大,就算使用到了线程池,但在系统层面上来讲,本质是重量级的系统函数,像Linux这样的操作系统,线程的本质上就是一个进程。
  2. 线程本身就占用了大量的空间,一般会分配 512Kb - 1M 的空间。
  3. 线程的切换成本是很高的,操作系统发生线程切换的时候,需要保留线程的上下文,如果线程数过高,那么可能线程的切换时间可能都会超过线程的执行时间,

核心部分

  1. Channel
  2. Buffers
  3. Selectors

Channel 和 Buffer

所有的 IO 在 NIO 中都是从一个Channel开始的,从英文意思来看,为通道,我们可以理解一个双向通道,数据可以双向传播。

[image:0A7BF8F2-7C3C-4766-82A7-02227E09BE52-1888-000006603DFC0EFF/overview-channels-buffers1.png]

在 Java 中,同样也对 Channel 进行了一些实现,如下

  • FileChannel 从文件中读写数据
  • DatagramChannel 面向UDP协议读写网络中的数据
  • SocketChannel 面向TCP协议读写网络中的数据
  • ServerSocketChannel 类似于ServerSocket可以对TCP进行监听,每一个请求都会创建一个SocketChannel

我们可以看到基本涵盖了 TCP、UDP、文件等 IO。

在 Java 的 NIO 中,我们既可以在通道读取数据,又可以在通道中写数据(双向),通道可以异步读写,在通道中的数据必须从 Buffer 中读取,或者从一个 Buffer 中写入。

Buffer 为缓冲区,在 Channel 中的数据交互都是通过 Buffer 来实现的。

以下是 Java 对 Buffer 的一些实现

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

在这里我们需要注意的是,StringBuffer 并不是在 NIO 中的内容。

Buffer的基本使用流程为:

  1. 写入数据到Buffer中
  2. 调用 filp() 方法,将 Buffer 从写模式转为读模式
  3. 从 Buffer 中读取数据
  4. 清空 Buffer 缓冲区 (clear() or compact()

注意,clear() 会清空整个 Buffer 缓冲区的数据,而 comapct() 方法只会清理已经读取过的数据,未读数据会移动到缓冲区的首,而后入数据则会追加到未读数据尾。

Selector

Selector 允许单线程处理多个通道的数据,原理是基于 epoll 多路复用机制。

如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。

[image:88C22BAA-BCEA-47A0-B6AC-7CDD340CEB8A-1888-00000662620EEC89/overview-selectors.png]

我们想要使用 Selector 首先就需要向 Selector 中注册 Channel,之后再调用select() 方法,这个方法会一直阻塞到某个 Channel 有事件就绪,一旦这个方法返回,那么 Selector 就能够处理这些事件。

NIO 文件读取例子

package NIO;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;

/**
 * @作者: Seale
 * @时间: 2021/05/01 8:17 下午
 * @说明:
 * @类名: NioFileTest
 */
public class NioFileTest {
    public static void main(String[] args) throws IOException {
        RandomAccessFile nioTest = new RandomAccessFile("/Users/seale/Dev/JavaPro/JavaStudy/src/NIO/test.txt","rw");
        FileChannel fileChannel = nioTest.getChannel();

        // init bytebuffer allocate 1024
        ByteBuffer buf = ByteBuffer.allocate(1024);
        CharBuffer cb = CharBuffer.allocate(1024);
        int byteRead = fileChannel.read(buf);
        Charset charset = StandardCharsets.UTF_8;
        CharsetDecoder charsetDecoder = charset.newDecoder();
        while (byteRead != -1){
            buf.flip(); // 更改写状态为读状态
            charsetDecoder.decode(buf,cb,true);
            cb.flip();
            char[] tmp = new char[cb.length()];
            while (cb.hasRemaining()){
                cb.get(tmp);
                System.out.print(new String(tmp));
            }
            buf.clear();
            cb.clear();
            byteRead = fileChannel.read(buf); // 继续读取下一区块
        }
        nioTest.close();
    }
}
测试nio
测试nio
测试nio
测试nio
测试nio

测试nio
测试nio
测试nio
测试nio

测试nio

测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio
测试nio

Process finished with exit code 0

本作品采用知识共享署名 4.0 国际许可协议进行许可。

如果可以的话,请给我钱请给我点赞赏,小小心意即可!

Last modification:May 1, 2021
If you think my article is useful to you, please feel free to appreciate