• NIO笔记



    JAVA NIO,是区别于JAVA IO的NEW IO。和普通的IO存在一定的区别,先记下这么几个字吧:

    走通道,用缓存,选择器,非阻塞


    走通道

    NIO有三大组成:Channel(通道)、Buffer(缓存)、Selector(选择器)
    其中通道是指的实现java.nio.channels.Channel接口的对象,常见的对象有以下几种:

    FileChannel(文件通道)
    DatagramChannel(收发UDP包的通道)
    SocketChannel(连接到TCP网络套接字的通道)
    ServerSocketChannel(可以监听新进来的TCP连接的通道)

    相比较于IO的时候,输入输出需要用InputStream,OutputStream两种,NIO的的通道只有一种,以FileChannel为例。
    打开:

    RandomAccessFile aFile = new RandomAccessFile("xxx.txt", "rw");
    FileChannel inChannel = aFile.getChannel();   
    

    读取:

    ByteBuffer buf = ByteBuffer.allocate(48); // nio的buffer
    int bytesRead = inChannel.read(buf); // 初始化写入到buffer
    while (bytesRead != -1) {
        buf.flip(); // 切换读写模式
        while(buf.hasRemaining()){
            System.out.print((char) buf.get()); // 读取buff的内容
        }
        buf.clear();  // buffer清空,使之可以再次写入
        bytesRead = inChannel.read(buf); // 写入到buffer
    }
    

    写入:

    inChannel.write(buf);
    

    可以看到,使用FileChannel不管是读取还是写入,都是使用了ByteBuffer作为中转,这个ByteBuffer,就是NIO中的缓存了。


    用缓存

    缓存类型:

    缓存是实现了java.nio.Buffer抽象类接口的对象。
    对于部分IO来说,其对象内部也存在缓存,但都是对象的成员变量,而NIO中的,缓存需要单独使用,走【数据】->【缓存】->【通道】->【对象】,或者【对象】->【通道】->【缓存】->【数据】的方式,进行数据的流通。
    缓存有很多种,除了boolean以外,基本类型全部包括。个人感觉有的类似于ObjectInputStream中writeByte、writeInt等等的方法,把这些方法抽象为一个对象的感觉。

    ByteBuffer
    CharBuffer
    DoubleBuffer
    FloatBuffer
    IntBuffer
    LongBuffer
    ShortBuffer

    例子:

    首先,可以看到,java.nio.Buffer的源码中的以下记得成员,这是buffer的关键:

    private int position = 0;
    private int limit;
    private int capacity;
    

    其中:

    capacity:内存块,初始化时设置,限制缓存空间大小。
    position:当前位置。写模式时初始为零,写一次移动一下。切换到读模式时,重置为0。position最大可为capacity – 1。
    limit:写模式时,同capacity,一次写入大小。切换到读模式时, limit表示你最多能读到多少数据,此时,limit会被设置成写模式下的position值。

    聚集和分散:

    简单的说,buffer使用的时候,可以一个数组一起使用,这种使用方式类似水流,先填满一个,再填满另一个。
    Scattering Reads(聚集)是指数据从一个channel读取到多个buffer中。

    ByteBuffer header = ByteBuffer.allocate(128);
    ByteBuffer body   = ByteBuffer.allocate(1024);
    ByteBuffer[] bufferArray = { header, body };
    channel.read(bufferArray);
    

    read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。

    Gathering Writes(分散)是指数据从多个buffer写入到同一个channel。

    channel.write(bufferArray);
    

    使用缓存:

    初始化:使用allocate方法,设定capacity以初始化

    ByteBuffer byteBuf = ByteBuffer.allocate(48);
    

    写入缓存:直降使用缓存的put方法,或者使用通道的read方法进行写入

    inChannel.read(buf); // 通道写入到buffer
    
    buf.put(100); // put写入
    

    模式切换:写入后,进行读取时,需要调用flip()方法,进行模式切换(实际是改变position位置)

    buf.flip(); // 切换读写模式
    

    重置:读取后想重新读取,使用rewind()方法。

    buf.rewind();  // buffer重置,使之可以再次读取
    

    清空:想要再次写入,或者清空写入内容时,使用clear()方法
    buf.clear(); // buffer清空,使之可以再次写入


    选择器

    选择器即Selector,简单的说,就是用于管理多个Channel的对象,感觉有的类似于线程池,但Selector确实是单线程对象,通过注册->接收->处理的过程,对多个Channel进行管理。
    其中将【选择器】同【通道】关联起来的线,即为选择键,SelectionKey。
    总之先记着,选择器使用静态实例创建,使用注册通道返回选择键的方式进行关联。

    Selector Selector=Selector.open();  // 创建实例
    channel.configureBlocking(false);  // 通道设置非阻塞,不然同Selector一起用会异常(FileChannel不能设非阻塞)
    SelectionKey key= channel.register(selector,SelectionKey.OP_ACCEPT); // 通道注册
    

    SelectionKey.OP_ACCEPT标识,是Selector要监视通道的那些动作。当通道的动作处在就绪状态时,Selector监视到,然后可进行操作。

    关于SelectionKey,既然是Channel和Selector的管理,那么进行操作的时候,也是对SelectionKey进行操作了。可以使用isAcceptable(),isConnectable(),isReadable(),isWritable(),来判断Channel的是否可以进行对应的操作。isValid判断是否有数据(缓存)。attach来进行绑定和解绑等等。
    因此,一般Selector的使用方式是弄个循环,使用selector.selectedKeys()的方法渠道所有就绪,能操作的SelectionKey,如下:

    while(true){ // 循环内
    	Set selectedKeys = selector.selectedKeys();  // 取得可操作SelectionKey集合
    	Iterator keyIterator = selectedKeys.iterator();
    	while(keyIterator.hasNext()) {
    	    SelectionKey key = keyIterator.next();  // 取得SelectionKey,并判断不同的状态进行操作
    	    if(key.isAcceptable()) {
    	        ...
    	    } else if (key.isConnectable()) {
    	        ...
    	    } else if (key.isReadable()) {
    	        ...
    	    } else if (key.isWritable()) {
    	        ...
    	    }
    	    keyIterator.remove();
    	}
    }
    

    非阻塞

    由于是同步非阻塞的,所以其原理基本上就是在设置好之后,利用循环不断遍历Channel,判断其状态,有可操作的地方就处理,没有则继续判断而已。
    其中,由于使用了缓存通道分离的方式,最大的优势在于当有可进行的操作的时候,实际上通道已经准备就绪,可以立即执行,省去了读取的时间。如下,其他方法时,以下read的时候,会阻塞花费较多时间。而NIO的时候,由于已经换成,会马上读取完毕,可执行下面的操作。

    SocketChannel socketChannel = (SocketChannel) key.channel();
    int len = socketChannel.read(readBuffer);  //  **这里**
  • 相关阅读:
    ecshop的详细安装步骤
    php+mysql 除了设置主键防止表单提交内容重复外的另一种方法
    strcmp
    map set区别
    ++i vs i++
    stl vector erase
    user initialization list vs constructor assignment
    default constructor,copy constructor,copy assignment
    memset
    strcpy vs memcpy
  • 原文地址:https://www.cnblogs.com/changfanchangle/p/9092450.html
Copyright © 2020-2023  润新知