NIO


前言

NIO在JDK1.4的时候开始登场,在JDK1.7的时候又出了NIO.2又称为AIO。而NIO的实现离不开操作系统对IO多路复用模型的支持,如果对IO模型的不太熟悉可以看这里。如果对IO操作中,读/写数据的流程不太熟悉,可以看这里。另外,本文没有对具体源码做分析

NIO和BIO的区别

NIO 与传统 I/O(BIO) 不同,它是基于块(Block)的,它以块为基本单位处理数据。而在IO中,基于流(stream)进行IO操作,如字节流以8位(一个字节)为单位进行IO操作,字符流以16个位为单位(Java采用Unicode字符集,一个字符占用两个字节)进行IO操作。

以网络IO为例子,当有30个客户端请求连接服务端时,假设服务端用一个线程来处理。

  • 按顺序处理1号到30号线程,即使1号线程,等了好久才有数据发送。

如果服务端采用多线程的方式,那么可以一下子创建30个线程,分别处理这个30个请求,但这种方式显然不太乐观,特别在面对百万客户端甚至更多。

而在NIO中,通过引入Selector等组件,帮服务端和客户端进行解耦。客户端的请求事件在Selector中进行注册,Selector帮我们监听连接中,哪个连接的数据已经到达,再通知到服务端线程。

此种状态下,服务端可能只需少量线程就能处理多个客户端的请求了。这种方式又叫做IO多路复用,这种设计模式又叫反应器模式

在这里插入图片描述

NIO实现

服务端栗子

public static void main(String[] args) throws Exception {
        // 1.得到一个ServerSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 2.得到一个选择器Selector对象
        Selector selector = Selector.open();
        // 3.绑定客户端的端口号
        serverSocketChannel.bind(new InetSocketAddress(8000));
        // 4.设置阻塞方式
        serverSocketChannel.configureBlocking(false);
        // 5.把ServerSocketChannel对象注册给Selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 6.监控客户端
        while (true) {
            // 表示没有客户端尝试连接
            if (selector.select(2000) == 0) {
                System.out.println("无连接");
                continue;
            }
            // 如果有的话,得到所有的SelectionKey,判断通道里的事件类型
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 判断selectionKey的事件类型
                if (selectionKey.isAcceptable()) {
                    // 客户端连接事件
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {
                    // 读取客户端数据事件
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
                    channel.read(buffer);
                    System.out.println("客户端发送的数据" + new String(buffer.array()));
                }
                iterator.remove();
            }
        }
    }

客户端栗子

public static void main(String[] args) throws Exception{
        // 1.先得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 2.设置阻塞方式为非阻塞
        socketChannel.configureBlocking(false);
        // 3.设置连接的服务器的IP和端口号
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8000);
        // 4.连接服务器
        if(!socketChannel.connect(inetSocketAddress)){
            // 如果没连接上, 不会一直阻塞, 可以处理其他任务
            while (!socketChannel.finishConnect()){
                System.out.println("处理其他任务....");
            }
        }
        // 5.得到一个缓冲区并存入数据
        String msg = "hello world!";
        ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
        // 6.发送数据
        socketChannel.write(byteBuffer);
        // 7.不能关闭连接,让客户端保持连接
        System.in.read();
    }

缓冲区

NIO种提供很多种类型的缓冲区:
在这里插入图片描述
图片来自这位博主的文章中

在这些个缓冲区中,有如下几个重要属性值:

  1. capacity:容量,表示的是这个缓冲区包含元素的个数
  2. limit:在 Buffer 上进行的读写操作都不能越过这个下标,代表 buffer 中有效数据的长度。
  3. position:表示当前的读或写到达哪个位置了。

关于缓冲区的读取,需要注意一下这几个属性位置关系:

  1. 初始化一个容量为7的缓冲区

在这里插入图片描述

  1. 往缓冲区写入2513,此时的位置变换如图

在这里插入图片描述

  1. 此时,如果我们只想按顺序读取部分数据,我们需要调用byteBuffer.flip()方法将 buffer 从写入操作转换成读取操作,不然从position位置开始读取的数据是空的。调用byteBuffer.flip()后如下:

在这里插入图片描述

  1. 同理 ,从读取操作转换成写入操作,需要使用buffer.clear()进行切换,但此时buffer中的数据并不会删除,只是修改了 position 、limit、和 mark 的位置。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

legendaryhaha

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

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

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

打赏作者

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

抵扣说明:

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

余额充值