Java大数据平台开发 学习笔记(58)—— JDK中IO的分类:BIO、NIO、AIO

一、概述

1.1、IO 的作用:传输数据

1.2、JDK中 IO 的分类:

  1. BIO - BlockingIO - 同步阻塞式IO
  2. NIO - NewIO - NonBlockingIO - 同步非阻塞式IO,在JDK1.4中出现 — tomcat
  3. AIO - AsynchronousIO - 异步非阻塞式IO,在JDK1.7中出现 - AIO本身是在NIO的基础上进行了改进,因此称之为NIO.2

注: 在现在开发中,如果需要进行大量的短任务,那么使用NIO;如果需要进行长任务,会使用BIO。

1.3、概念回顾 (同步、异步、阻塞、非阻塞)

  1. 同步:一个对象或者一段逻辑在一个时间段内只能被一个线程占用
  2. 异步:一个对象或者一段逻辑在一个时间段内允许被多个线程抢占
  3. 阻塞:线程在获取到结果之前会持续等待,既不往下执行代码也不报错
  4. 非阻塞:线程无论是否获取到结果,都会继续往下执行或者报错

二、BIO的缺点

  • 阻塞:阻塞必然导致效率降低
  • 一对一连接:每次客户端发起连接,服务器端都需要产生一个线程去处理这个连接。如果有大量的客户端发起连接,那么就意味着服务器端需要产生大量线程去处理请求。服务器端的线程如果过多,就会导致服务器的卡顿甚至崩溃
  • 无效连接:在BIO中,无法处理无效的空连接。如果有大量的无线连接产生,就会大量占用服务器端的线程。此时依然可能会导致服务器的卡顿甚至于崩溃

三、NIO的三大组件:

3.1、Buffer - 缓冲区

  1. 作用:临时存储数据
  2. Buffer底层是基于数组来进行存储的,只能存储基本类型的数据。Buffer针对八种基本类型,提供了7个实现类,唯独没有针对boolean类型的实现类
  3. 重要位置:capacity >= limit >= position
  4. capacity:容量位。用于标记缓冲区的容量,指向缓冲区的最后一位。容量位不可变
  5. position:操作位。类似于数组中的下标,position指向哪一位就会读写哪一位。每次读写完成之后position会自动的后挪一位。在缓冲区刚创建的时候,position默认执行第0位
  6. limit:限制位。用于限定position所能达到的最大下标的。当limit和position重合的时候,表示所有的数据都已经被读写完。在缓冲区刚创建的时候,limit默认和capacity重合

3.2、Channel - 通道

  1. 作用:传输数据
  2. Channel可以实现双向传输
  3. Channel默认是阻塞的,但是可以手动设置为非阻塞
3.2.1、常见的Channel
  • 文件:FileChannel
  • UDP:DatagramChannel
  • TCP:SocketChannel,ServerSocketChannel

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

3.3、Selector - 多路复用选择器

  1. 作用:针对事件(客户端和服务器端之间能够产生的操作)来进行选择。
  2. 实际生产过程中,会考虑将选择器放在服务器端来设置。
  3. Selector是面向Channel操作,要求Channel必须是非阻塞的。

四、代码实例:

3.1、Buffer - 缓冲区

public class ByteBufferDemo2 {
    
    

    public static void main(String[] args) {
    
    

        // byte[] arr = new byte[n] - 数据未知
        // ByteBuffer buffer = ByteBuffer.allocate();
        // 数据已知
        // byte[] arr = {n1, n2, n3 ...}
        // ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes());
        // buffer.put("a".getBytes());

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("hello big2007 ~~~".getBytes());
        // 获取底层对应的字节数组
        // 如果改变数组中的数据,缓冲区中的数据也会跟着变
        byte[] arr = buffer.array();
        // arr[0] = 97;
        // System.out.println(new String(arr, 0, buffer.position()));
        buffer.flip();
        System.out.println(new String(arr, 0, buffer.limit()));
    }
}

3.2、Channel - 通道

3.2.1、Server 服务端

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();
    }
}

3.3.2、Client 客户端

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();
    }
}

3.3、Selector - 多路复用选择器

3.3.1、Server 服务端

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();
            }
        }
    }
}

3.3.2、Client 客户端

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();
    }
}


• 由 ChiKong_Tam 写于 2020 年 12 月 28 日

猜你喜欢

转载自blog.csdn.net/qq_42209354/article/details/111831357