• 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。
  • 相关阅读:
    10-JavaScript 条件语句
    9-JavaScript 比较
    8-JavaScript 运算符
    6-JavaScript 事件
    Sum Problem 重定向文件的使用
    Calculate A + B.
    Vue中computed的本质及与methods的区别
    外部文件使用django的models
    DjangoURL反向解析
    字符串格式化的方式
  • 原文地址:https://www.cnblogs.com/wanghq1994/p/12173062.html
Copyright © 2020-2023  润新知