NIO中的三大组件:Buffer、Channel、Selector基础知识,小白须知

一、NIO

1.它是同步非阻塞式IO
2.同步:一个对象在某个时段,只能被一个线程使用
3.非阻塞:线程无论是否获取到结果,都会继续往下执行或者报错
4.IO:传输数据

二、NIO三大组件

1.Buffer(缓冲区)

⑴作用:临时存储数据
⑵Buffer底层是基于数组来进行存储的,只能存储基本类型的数据。Buffer针对八种基本类型,提供了7个实现类,唯独没有针对boolean类型的实现类
⑶重要位置:capacity >= limit >= position
      ①capacity:容量位。用于标记缓冲区的容量,指向缓冲区的最后一位。容量位不可变
      ②position:操作位。类似于数组中的下标,position指向哪一位就会读写哪一位。每次读写完成之后position会自动的后挪一位。在缓冲区刚创建的时候,position默认执行第0位
      ③limit:限制位。用于限定position所能达到的最大下标的。当limit和position重合的时候,表示所有的数据都已经被读写完。在缓冲区刚创建的时候,limit默认和capacity重合
⑷常用方法
buffer.flip()                         翻转缓冲区
buffer.wrap()                      数据已知
ByteBuffer.allocate()          构建ByteBuffer对象,参数实际上是指底层的字节数组的容量
buffer.capacity()                 获取容量位
buffer.hasRemaining()       获取当前位置到限制位是否有元素

public class ByteBufferDemo {
    public static void main(String[] args) {
        //构建ByteBuffer对象
        //参数实际上是指底层的字节数组的容量
        ByteBuffer buffer = ByteBuffer.allocate(10);
        //获取容量位
        System.out.println(buffer.capacity());
        //添加数据
        buffer.put("abc".getBytes());
        buffer.put("def".getBytes());
        /*//操作操作位
        buffer.position(0);
        //获取数据
        byte b = buffer.get();
        System.out.println(b);*/
        //将limit和position重合
        /*buffer.limit(buffer.position());
        //再将position归零
        buffer.position(0);
        //上面两步操作,叫做翻转缓冲区*/
        buffer.flip();
        //while (buffer.position()<buffer.limit()){
        while (buffer.hasRemaining()){
            byte b = buffer.get();
            System.out.println((char)b);
        }




    }
}

2.Channel(通道)

⑴作用:传输数据
⑵Channel可以实现双向传输
⑶Channel默认是阻塞的,但是可以手动设置为非阻塞
⑷常见的Channel
文件: FileChannel

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

        // r --- 只读方式
        // rw --- 读写方式
        // rwd --- 读写方式,不同于rw地方在于,要求每次写入的数据必须更新到磁盘上而不是内存中
        // RandomAccessFile将整个文件看作是一个大型的字节数组
        // 在RandomAccessFile中,每次读写完之后,下标会自动后挪
        RandomAccessFile raf = new RandomAccessFile("e:\\b.txt", "rw");
        // 获取到channel
        FileChannel fc = raf.getChannel();
        // 读取数据
        ByteBuffer buffer = ByteBuffer.allocate(3);
        fc.read(buffer);
        System.out.println(new String(buffer.array(), 0, 3));
        // 指定在文件中的写入位置
        raf.seek(20);
        // 写出数据
        fc.write(ByteBuffer.wrap("abc".getBytes()));
        // 关流
        fc.close();

    }

UDP:DatagramChannel
TCP:SocketChannel,ServerSocketChannel

public class Server {

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

        // 开启服务器端通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 绑定端口
        ssc.bind(new InetSocketAddress(8090));
        // 设置为非阻塞
        ssc.configureBlocking(false);
        // 接收连接
        SocketChannel sc = ssc.accept();
        // 判断是否接收到了连接
        while (sc == null)
            sc = ssc.accept();
        // 连接建立
        // 读取数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        // 解析数据
        System.out.println(new String(buffer.array(), 0, buffer.position()));
        // 关流
        ssc.close();

    }

}
public class Client {

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

        // 开启客户端通道
        SocketChannel sc = SocketChannel.open();
        // 手动设置为非阻塞
        sc.configureBlocking(false);
        // 发起连接 - 阻塞
        // 非阻塞:无论是否建立连接,都会继续往下执行
        sc.connect(new InetSocketAddress("localhost", 8090));
        // 判断连接是否建立
        // 如果多次连接没有成功,说明这个连接无法建立
        while (!sc.isConnected()) {
            // 试图重新建立连接
            // 这个方法会自动进行计数,多次计数之后如果仍然无法建立连接,会抛出异常
            sc.finishConnect();
        }
        // 连接建立
        // 写出数据
        sc.write(ByteBuffer.wrap("hello server".getBytes()));
        // 关闭连接
        sc.close();

    }

}

⑸利用FileChannel实现"Zero-copy"(零拷贝)机制 - 零拷贝指的并不是没有数据传输而是没有状态的转化
⑹Channel都是面向Buffer进行操作

3.Selector(多路复用选择器)

⑴作用:针对事件(客户端和服务器端之间能够产生的操作)进行选择

public class Client {

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

        // 开启客户端通道
        SocketChannel sc = SocketChannel.open();
        // 发起连接
        sc.connect(new InetSocketAddress("localhost", 8070));
        // 写出数据
        sc.write(ByteBuffer.wrap("hi from client".getBytes()));
        // 读取数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        System.out.println(new String(buffer.array(), 0, buffer.position()));
        // 关流
        sc.close();

    }

}
public class Server {

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

        // 开启服务器端的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 绑定端口
        ssc.bind(new InetSocketAddress(8070));
        // 设置非阻塞
        ssc.configureBlocking(false);
        // 获取选择器
        // 实际过程中,将选择器设置为单例的
        Selector sel = Selector.open();
        // 将通道注册到选择器上,指定要注册的事件
        ssc.register(sel, SelectionKey.OP_ACCEPT);
        // 实际生产过程中,服务器开了应该不会关闭
        // 用while(true)来模拟服务器一直开着
        while (true) {
            // 当服务器一直开着的时候,随着运行时间的延长,服务器就会接收到越来越多的请求
            // 不代表这些请求都是有用请求 - 需要进行选择,选出有用的请求
            sel.select();
            // 确定这一些请求可能会出发的操作:accept/read/write
            Set<SelectionKey> set = sel.selectedKeys();
            // 遍历集合,根据请求的类型不同来分别处理
            Iterator<SelectionKey> it = set.iterator();
            while (it.hasNext()) {
                // 获取到请求类型
                SelectionKey key = it.next();
                // 可接受事件
                if (key.isAcceptable()) {
                    // 先从选择器中获取到通道
                    ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
                    // 接收请求
                    SocketChannel sc = sscx.accept();
                    // 设置非阻塞
                    sc.configureBlocking(false);
                    // 注册可读事件
                    // sc.register(sel, SelectionKey.OP_READ);
                    // 注册可写事件
                    // sc.register(sel, SelectionKey.OP_WRITE);
                    // 在注册事件的时候,后注册的事件会覆盖之前注册的事件
                    // sc.register(sel, SelectionKey.OP_READ + SelectionKey.OP_WRITE);
                    // sc.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                    sc.register(sel, SelectionKey.OP_READ ^ SelectionKey.OP_WRITE);
                }
                // 可读事件
                if (key.isReadable()) {
                    // 获取通道
                    SocketChannel sc = (SocketChannel) key.channel();
                    // 读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    sc.read(buffer);
                    // 解析数据
                    System.out.println(new String(buffer.array(), 0, buffer.position()));
                    // 注销可读事件
                    // key.interestOps()获取到当前通道上的所有事件 --- 再从这些事件中抠掉可读事件
                    // sc.register(sel, key.interestOps() - SelectionKey.OP_READ);
                    sc.register(sel, key.interestOps() ^ SelectionKey.OP_READ);
                }
                // 可写事件
                if (key.isWritable()) {
                    // 获取通道
                    SocketChannel sc = (SocketChannel) key.channel();
                    // 写出数据
                    sc.write(ByteBuffer.wrap("hello client".getBytes()));
                    // 注销可写事件
                    sc.register(sel, key.interestOps() - SelectionKey.OP_WRITE);
                }
                // 移除
                it.remove();
            }
        }

    }

}

⑵选择器放在服务端设置
⑶Selector是面向Channel操作,要求Channel必须是非阻塞的

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值