java NIO学习

Posted good good study

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java NIO学习相关的知识,希望对你有一定的参考价值。

NIO的全称是non-blocking IO,也就是非阻塞IO,也有的人叫他New IO。他的核心内容主要有三部分,Channel(通道),Buffer(缓冲区), Selecto(选择器)。下面我们针对这三部分详细了解一下NIO。

Buffer(缓冲区)

Buffer缓冲是一个指定固定数据量的容器,一个连续数组。除内容之外,缓冲区还具有位置和界限,其中位置是要读写的下一个元素的索引,界限是第一个应该读写的元素的索引。java中每个非布尔基本类型都有一个缓冲区类。

Buffer通过capacity, position, limit, mark这四个变量来保存这个数据的当前位置状态,下面介绍一下这四个属性的意义。

  • capacity(容量值):缓冲区数组的总长度
  • position(位置):下一个要操作的数据元素的位置
  • limit(极限):缓冲区数组中不可操作的下一个元素的位置
  • mark(标记):用于记录当前position的位置,默认是-1

基本 Buffer 类定义了这些属性以及清除、反转和重绕方法,用以标记当前位置,以及将当前位置重置为前一个标记处。

  • clear()使缓冲区准备好信道读取或相对放置操作的一个新的序列:它设置了限制的能力和位置为零。

  • flip()使缓冲区准备好新的通道写入或相对获取操作序列:它将限制设置为当前位置,然后将位置设置为零。

  • rewind()使缓冲区准备好重新读取已经包含的数据:它保持限制不变,并将位置设置为零。

举例说明Buffer中各个属性:

package stream.nio;

import java.nio.ByteBuffer;

public class BufferTest {
    public static void main(String[] args) {
        //创建一个10个字节的缓冲区,有两种方法
        byte[] bytes = new byte[10];
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        //ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        System.out.println("创建缓冲区后的buffer信息为...");
        printBufferInfo(byteBuffer);
        //为缓冲区赋值
        for (int i = 0; i < 5; i++) {
            byteBuffer.put((byte) i);
        }
        System.out.println("初始赋值后的buffer信息为...");
        printBufferInfo(byteBuffer);

        //bufferTest1(byteBuffer);
        //bufferTest2(byteBuffer);
    }

    /**
     * 打印Buffer中的信息
     * @param byteBuffer
     */
    public static void printBufferInfo(ByteBuffer byteBuffer) {
        System.out.println("limit = " + byteBuffer.limit());
        System.out.println("capacity = " + byteBuffer.capacity());
        System.out.println("position = " + byteBuffer.position());
    }

    public static void bufferTest1(ByteBuffer byteBuffer) {
        //调用mark方法,标记此时的position
        byteBuffer.mark();

        //继续为缓冲区赋值
        byteBuffer.put((byte) 6);
        byteBuffer.put((byte) 7);
        byteBuffer.put((byte) 8);
        System.out.println("再次赋值后的buffer信息为...");
        printBufferInfo(byteBuffer);

        System.out.println("-----------reset()方法---------------");
        //调用reset方法,回到mark标记的position位置
        byteBuffer.reset();

        System.out.println("调用reset方法后的buffer信息为...");
        printBufferInfo(byteBuffer);

        System.out.println("打印buffer position后的内容...");
        while (byteBuffer.remaining() > 0) {
            //调用此方法后,会影响position的位置
            System.out.print(byteBuffer.get());
        }
        System.out.println();
        System.out.println("-----------reset()方法---------------");

        System.out.println("打印此时buffer信息为...");
        printBufferInfo(byteBuffer);
    }

    public static void bufferTest2(ByteBuffer byteBuffer) {

        //调用flip方法,转换缓冲区的可操作性位置
        byteBuffer.flip();
        System.out.println("调用flip方法后的buffer信息为...");
        printBufferInfo(byteBuffer);

        System.out.println("-----------rewind()方法---------------");
        System.out.println("读取缓冲区中的信息...");
        while (byteBuffer.remaining() > 0) {
            System.out.print(byteBuffer.get());
        }
        System.out.println();

        //调用rewind方法
        byteBuffer.rewind();
        System.out.println("调用rewind方法后再次读取缓冲区中的信息...");
        while (byteBuffer.remaining() > 0) {
            System.out.print(byteBuffer.get());
        }
        System.out.println();
        System.out.println("-----------rewind()方法---------------");

        //调用clear方法,转换缓冲区的可操作性位置
        byteBuffer.clear();
        System.out.println("调用clear方法后的buffer信息为...");
        printBufferInfo(byteBuffer);
    }

    public static void bufferTest3(ByteBuffer byteBuffer) {
        //创建子缓存区
        byteBuffer.position(3);
        byteBuffer.limit(7);
        ByteBuffer slice = byteBuffer.slice();

        //子缓存区重新赋值
        for (int i = 0; i < slice.capacity(); i++) {
            slice.put((byte) i);
        }

        byteBuffer.position(0);
        byteBuffer.limit(byteBuffer.capacity());

        //打印之前的缓存区,值变成子缓存区的内容了
        while (byteBuffer.remaining() > 0) {
            System.out.println(byteBuffer.get());
        }
    }
}

打印结果如下:

创建缓冲区后的buffer信息为...
limit = 10
capacity = 10
position = 0
初始赋值后的buffer信息为...
limit = 10
capacity = 10
position = 5

打开bufferTest1()方法测试mark()方法。运行结果如下:

再次赋值后的buffer信息为...
limit = 10
capacity = 10
position = 8
-----------reset()方法---------------
调用reset方法后的buffer信息为...
limit = 10
capacity = 10
position = 5
打印buffer position后的内容...
67800
-----------reset()方法---------------
打印此时buffer信息为...
limit = 10
capacity = 10
position = 10

 可以发现,reset()方法确实将position值设置到了调用mark()方法时的位置。而且读取完buffer信息后,position的位置也到了所定义的最大长度。

注释bufferTest1()方法,打开bufferTest2()方法测试rewind()等方法。运行结果如下:

调用flip方法后的buffer信息为...
limit = 5
capacity = 10
position = 0
-----------rewind()方法---------------
读取缓冲区中的信息...
01234
调用rewind方法后再次读取缓冲区中的信息...
01234
-----------rewind()方法---------------
调用clear方法后的buffer信息为...
limit = 10
capacity = 10
position = 0

 从结果可知,调用flip()方法,其实就是将读取写入的部分。我们刚刚写入了五个数据,所以我们也就之能读取0-4位置的数据。rewind()方法是可以让我们重新读取一次。之前读过的数据不调用clear()方法,是不会被清除掉。

Channel(通道)

Channel是一个对象,可以通过它读取和写入数据。Channel和传统IO中的Stream很相似。主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如InputStream只能进行读取操作,OutputStream只能进行写操作;但是Channel中的所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

NIO中Channel的主要实现有:FileChannel(文件通道),DatagramChannel(UDP包通道),SocketChannel(socket客户端通道),ServerSocketChannel(socket服务端通道)。SocketChannel模拟连接导向的流协议(如TCP/IP),DatagramChannel则模拟包导向的无连接协议(如UDP/IP)。
Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。Channel和Buffer的关系如图:

Selector(选择器)

Selector(选择器)提供了选择已经就绪的任务的能力。Selector会不断的轮询注册在上面的所有channel,如果某个channel为读写等事件做好准备,那么就处于就绪状态,对就绪状态的channel进行后续的IO操作。一个Selector能够同时轮询多个channel。这样,一个单独的线程就可以管理多个channel,从而管理多个网络连接。这样就不用为每一个连接都创建一个线程,同时也避免了多线程之间上下文切换导致的开销。
与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,主要有OP_ACCEPT(用于套接字准备接受操作位)、OP_CONNECT(用于套接字连接操作的操作位)、OP_READ(读操作的操作位)、OP_WRITE(写操作的操作位)。通过判断SelectionKey处于什么样的状态,进而做对应的操作。


为了避免篇幅太长,影响阅读体验,同时也是想对NIO一层一层的了解,一下灌入太多也不好,所以打算分几篇来介绍学习。上面说了一些概念的东西以及Buffer的相关使用,下一篇将会探究一下Channel和Selector。

以上是关于java NIO学习的主要内容,如果未能解决你的问题,请参考以下文章

Java NIO学习

Java NIO学习二

java NIO学习笔记上(尚硅谷)

Java之NIO

Java NIO学习笔记八 DatagramChannel

从Netty到EPollSelectorImpl学习Java NIO