IO基础回顾

BIO

说到Java IO流,就会想到OutputStream(字节输出流)、InputStream(字节输入流)、Writer(字符输出流)、Reader(字符输入流) 这些概念。

image.png

IO操作提供了操作 本地文件、网络资源、数据库读写 等功能,这些都是需要和磁盘或者网络打交道的,这类操作叫做耗时操作,容易导致服务阻塞。当然像 ByteArrayInputStreamByteArrayOutputStream 这种只操作内存的IO流除外。

所以BIO叫做 同步阻塞IO,可以通过线程池的方式解决,将每个IO操作单独分配给一个线程,这样主线程就不会被阻塞了。线程池的方式也有效避免了线程重复创建和销毁导致的资源消耗

NIO

JDK1.4以后为我们提供了NIO工具类,在 java.nio 包下
NIO可以在只有一个线程的情况下实现非阻塞操作,主要是通过 Buffer(缓冲区)、Channel(通道)、Selector(选择器) 来实现的。

image.png

客户端向服务端发起连接时会先获取到一个SocketChannel,Selector会用一个集合去存储绑定这个Channel的SelectionKey。客户端发数据不会直接将数据实时写入服务端,而是先将数据写入到本地Buffer中,Buffer就起到一个缓冲区的作用,如果缓冲区满了,就向Channel中写入数据,Selector监听到某个SelectionKey有事件发生,就通过这个SelectionKey获取到这个Channel,然后从Channel中读取数据。这样设计的好处就是,Server不需要一直和Client保持连接,而是Client需要Server去处理数据的时候Server再去处理。这样Server就可以同时处理多个IO操作,如果都没有IO请求,Server甚至还可以干自己的事情。

服务端在单线程情况下(也是可以用多线程的,Netty就是基于NIO的,Netty可以创建线程池去处理IO操作)使用一个Selector循环遍历存储SelectionKey的集合,如果某个SelectionKey有事件发生,Selector就处理这个SelectionKey对应的Channel中的数据。

NIO特点是非阻塞、基于事件监听的机制,所以NIO又叫 同步非阻塞IO

AIO

jdk1.7提供支持
简单来说,就叫 异步非阻塞IO,用多线程的方式处理IO。这样一看好像和NIO没啥区别嘛。NIO也是非阻塞的,搞个线程池不就是AIO了?

实际上他们区别大了,上面说NIO是通过Selector遍历所有的Channel去实现事件监听的,这么做其实是 伪事件监听 的做法。

就好比,在医院,5个小伙伴打吊针,他们分别在1~5楼,如果是NIO的方式,只需要一个护士小姐姐从一楼到五楼都走上一遍,看他们的点滴瓶子空了没,这还是个反复的过程。如果是BIO,一个还不够,需要5个小姐姐盯着自己对号的小伙伴。AIO在NIO的基础上更近一层,通常也叫NIO2.0。AIO实现了真正的事件监听,如果哪个小伙伴点滴瓶子空了,不需要小姐姐再过来检查了,而是会自己通知小姐姐过来。中间这段时间,小姐姐就不用再到处跑了,可以安心在自己办公室做个面膜。可以看得出来,AIO比NIO在对系统资源的调度方面更进了一步。

AIO事件通知需要操作系统的配合。

Buffer

在NIO中,Buffer起到了缓冲数据的作用,也是非阻塞实现的关键,Jdk为我们预定义了一些Buffer实现类
image.png
可以看到,是一些基本类型的实现。其中ByteBuffer通常可以用来传输音频,视频,图像等格式,CharBuffer可以传文本字符串

以IntBuffer为例

IntBuffer intBuffer = IntBuffer.allocate(5);

这样就构建了一个 Int类型的Buffer对象,大小为5,如果超出了5个,就会抛出 java.nio.BufferOverflowException

详细例子:

public static void main(String[] args) {
    IntBuffer intBuffer = IntBuffer.allocate(5);
    for (int i = 0; i < intBuffer.capacity(); ++i) {
    	intBuffer.put(i);
    }

    // 超出buffer容量,抛出异常 java.nio.BufferOverflowException
    // allocate.put(10);

    // 读写切换
    intBuffer.flip();

    while (intBuffer.hasRemaining()) {
    	System.out.println(intBuffer.get());
    }
}

需要注意,Buffer是半双工的,只能在同一时间读取或写入,不能同时读写,如果需要读写切换,请调用 flip 方法

Channel

继承关系如下,例举几个常用的:FileChannel(本地文件读写通道),SocketChannel(网络客户端读写通道),ServerSocketChannel(网络服务端通道)
image.png

可以直接通过Jdk原生的IO对象获取,比如 FileInputStream 对象调用 getChannel 方法获取 FileChannel

public static void main(String[] args) {
    String path = ChannelDemo.class.getClassLoader().getResource("in.txt").getPath();
    try {
        FileChannel channel = new FileInputStream(path).getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        buffer.flip();
        System.out.println(new String(buffer.array(), 0, buffer.limit()));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

读取resources目录的in.txt文件,获取Channel对象。创建一个Buffer,将数据读入Buffer中,然后打印在控制台上

调用Buffer的 flip 方法会将写入流转变成读取流,并将limit赋值为写入流最后的position位置(也就是最后写入字符的位置),不然的话会读取出一堆空字符串。

看flip源码

public final Buffer flip() {
    limit = position;	// 读取的时候只读到最后写入位置就行了
    position = 0;	// 读取的时候从第一个位置开始读
    mark = -1;
    return this;
}

BIO和NIO最主要的区别就是,BIO并没有通道和缓冲区的概念,只是单纯的将流中的数据读取到另一个写入流或者(字节数组/字符串中)

Selector

Selector中有个Set集合,用来存放所有与Channel绑定的SelectionKey
通过不断遍历这个Set,监听每个SelectionKey是否有事件发生
SelectionKey有4中事件类型

// 读事件
public static final int OP_READ = 1;
// 写事件
public static final int OP_WRITE = 4;
// 连接就绪,只有一次
public static final int OP_CONNECT = 8;
// 接收连接,包括OP_READ和OP_WRITE
public static final int OP_ACCEPT = 16;

注意Selector中的 select() 是阻塞的,它会一直等待直到有事件发生。
select(long timeout) 是非阻塞的,如果指定时间内没有接收到事件,则会退出。selectNow() 也是非阻塞的,他不会等待,如果没有事件,就直接退出。

# Netty  NIO 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×