第9节复用模型三大组件详解

缓冲区

概述

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如图: 【后面举例说明】

在这里插入图片描述

由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。除了Boolean外其他所有的基本数据类型都有缓冲区。

在这里插入图片描述

Java NIO 中的 Buffer 主要用于与 NIO 通道进行 交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。

在这里插入图片描述

缓冲区的基本属性

  • 容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
  • 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
  • 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制
  • 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position.
  • 标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

在这里插入图片描述

常用方法

  1. capacity()----返回的是缓冲区容量的大小。
  2. position()----返回position的值大小。
  3. limit()----返回此缓冲区的限制值limit.
  4. mark()----标记缓冲区中位置。
  5. reset()----将缓冲区位置position设置为标记的位置。
  6. clear()----清空缓冲区,调用此方法将position设置为0,limit的值设置为缓冲区容量值,标记将被丢弃。
  7. flip()----调用此方法将limit设置当前position的值,position设置为0,有标记情况下,标记将被丢弃。
  8. rewind()----调用此方法将position值设置为0,标记将被丢弃.,limit值不会变化。
  9. remaining()----返回当前位置与限制limit之间的值,即limit-position的值。
  10. hasRemaing()—布尔类型,当前位置与限制值之间是否有元素。
  11. isDirect()----判断此缓冲区是否为直接缓冲区(调用allocateDirect()分配的是直接缓冲区,即不受JVM管控的堆外内存)。

缓冲区相关操作

创建缓冲区

ByteBuffer buf1 = ByteBuffer.allocate(1024); //调用allocate()方法,创建直接缓冲区
ByteBuffer buf2 = ByteBuffer.allocateDirect(1024); //调用allocateDirect()方法,创建非直接缓冲区

读取缓冲区中的数据与将数据放入缓冲区

如何读取缓冲区的数据
直接看图
_
在这里插入图片描述

实例

  • 1.写入数据到Buffer
  • 2.调用flip()方法,转换为读取模式
  • 3.从Buffer中读取数据
  • 4.调用buffer.clear()方法或者buffer.compact()方法清除缓冲区
package com.pjh.Buffer;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.junit.jupiter.api.Test;

import java.nio.ByteBuffer;

/**
 * @ClassName: TestBuffer2
 * @Author: 86151
 * @Date: 2021/7/23 21:42
 * @Description: TODO
 */
public class TestBuffer2 {
    @Test
    public void test(){
        /*1.创建一个缓冲区并分配1024个字节的大小*/
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        System.out.println("-----------------buffer()----------------");
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity:"+buffer.capacity());

        /*2.利用put将数据存入缓冲区*/

        String str="abcd";
        buffer.put(str.getBytes());

        System.out.println("-----------------put()----------------");
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity:"+buffer.capacity());

        /*3.切换倒读取模式*/
        buffer.flip();

        System.out.println("-----------------filp()----------------");
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity:"+buffer.capacity());

        /*4.利用get()获取数据*/
        byte[] bytes = new byte[buffer.limit()];

        buffer.get(bytes);
        System.out.println("get():"+new String(bytes,0, bytes.length));
        System.out.println("-----------------get()----------------");
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity:"+buffer.capacity());

        /*5.rewind()可重复读*/
        buffer.rewind();

        System.out.println("-----------------rewind()----------------");
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity:"+buffer.capacity());

        /*5.clear(): 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态*/
        buffer.clear();

        System.out.println("-----------------clear()----------------");
        System.out.println("position:"+buffer.position());
        System.out.println("limit:"+buffer.limit());
        System.out.println("capacity:"+buffer.capacity());
    }

    @Test
    public void test2(){
        String str="abcdefghijk";

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put(str.getBytes());

        /*将position至为0*/

        buffer.flip();
        byte[] dst = new byte[buffer.limit()];
        buffer.get(dst, 0, 2);
        System.out.println(new String(dst, 0, 2));
        System.out.println(buffer.position());


        //mark() : 标记
        buffer.mark();

        buffer.get(dst, 2, 2);
        System.out.println(new String(dst, 2, 2));
        System.out.println(buffer.position());

        //reset() : 恢复到 mark 的位置
        buffer.reset();
        System.out.println(buffer.position());

        //判断缓冲区中是否还有剩余数据
        if(buffer.hasRemaining()){

            //获取缓冲区中可以操作的数量
            System.out.println(buffer.remaining());
        }
    }

    @Test
    public void test3(){
        /*分配直接缓冲区*/
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

        System.out.println(buffer.isDirect());
    }
}

直接缓冲区与非直接缓冲区

byte byffer可以是两种类型,一种是基于直接内存(也就是非堆内存);另一种是非直接内存(也就是堆内存)。对于直接内存来说,JVM将会在IO操作上具有更高的性能,因为它直接作用于本地系统的IO操作。而非直接内存,也就是堆内存中的数据,如果要作IO操作,会先从本进程内存复制到直接内存,再利用本地IO处理。

  1. 直接缓冲区的创建是由操作系统方面的代码分配的,是JVM堆外的内存,直接分别缓冲区脱离了JVM的束缚,是IO操作方面的最佳选择,但创建和销毁直接缓冲区花销更大

  2. 如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。

  3. 非直接缓冲区分配的是JVM堆内存。

  4. 创建非直接缓冲区向通道传输数据底层可能会先创建一个临时直接缓冲区,然后将非直接缓冲区数据放到临时直接缓冲区,使用临时缓冲区与IO进行交互,这会创建大量对象,但垃圾对象能得到及时回收

直接内存与非直接内存的IO区别

从数据流的角度,非直接内存是下面这样的作用链:

本地IO-->直接内存-->非直接内存-->直接内存-->本地IO

而直接内存是:

本地IO-->直接内存-->本地IO

直接或非直接缓冲区只针对字节缓冲区而言。字节缓冲区是那种类型可以通过 isDirect() 方法来判断。

非直接缓冲区属于常规操作,传统的 IO 流和 allocate() 方法分配的缓冲区都是非直接缓冲区,建立在 JVM 内存中。这种常规的非直接缓冲区会将内核地址空间中的内容拷贝到用户地址空间(中间缓冲区)后再由程序进行读或写操作,换句话说,磁盘上的文件在与应用程序交互的过程中会在两个缓存中来回进行复制拷贝。

在这里插入图片描述

而直接缓冲区绝大多数情况用于显著提升性能,缓冲区直接建立在物理内存(相对于JVM 的内存空间)中,省去了在两个存储空间中来回复制的操作,可以通过调用 ByteBuffer 的 allocateDirect() 工厂方法来创建。直接缓冲区中的内容可以驻留在常规的垃圾回收堆之外,因此它们对应用程序的内存需求量造成的影响可能并不明显。另外,直接缓冲区还可以通过 FileChannel 的 map() 方法将文件直接映射到内存中来创建,该方法将返回 MappedByteBuffer 。
**

在这里插入图片描述

注意!!!直接缓冲区性能虽然好,但是缓冲区直接建立在物理内存中,无法由 GC来释放,可控性差,同时分配和销毁成本很高!在对性能不是特别依赖的场景不建议使用!

很明显,在做IO处理时,比如网络发送大量数据时,直接内存会具有更高的效率。直接内存使用allocateDirect创建,但是它比申请普通的堆内存需要耗费更高的性能。不过,这部分的数据是在JVM之外的,因此它不会占用应用的内存。所以呢,当你有很大的数据要缓存,并且它的生命周期又很长,那么就比较适合使用直接内存。只是一般来说,如果不是能带来很明显的性能提升,还是推荐直接使用堆内存。字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。

通道

概述

由 java.nio.channels 包定义。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

Channel**用于在字节缓冲区和位于通道另一侧的实体(通常是文件或者套接字)**之间以便有效的进行数据传输。借助通道,可以用最小的总开销来访问操作系统本身的I/O服务。

**通道必须结合Buffer使用,**不能直接向通道中读/写数据,其结构如下图:

1.通道类似于流,但是有些区别如下

  • 通道可以同时进行读写,而流只能读或者只能写
  • 通道可以实现异步读写数据
  • 通道可以从缓冲读取数据,也可以写数据到缓冲

2.基本介绍 BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。

3.Channel在NIO中是一个接口public interface Channel extends Closeable{}

常用的Channel实现类

  • FileChannel:用于读取、写入、映射和操作文件的通道。
  • DatagramChannel:通过 UDP 读写网络中的数据通道。
  • SocketChannel:通过 TCP 读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】

获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:

FileInputStream
FileOutputStream
RandomAccessFile
DatagramSocket
Socket
ServerSocket

获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道

FileChannel()类

概述

Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。文件通道总是阻塞式的,因此FileChannel无法设置为非阻塞模式.

同大多数 I/O 相关的类一样,FileChannel是一个反映Java虚拟机外部一个具体对象的抽象。FileChannel类保证同一个Java虚拟机上的所有实例看到的某个文件的视图均是一致的,但是Java虚拟机却不能对超出它控制范围的因素提供担保。通过一个FileChannel实例看到的某个文件的视图与通过一个外部的非Java进程看到的该文件的视图可能一致,也可能不一致。一般而言,由运行在不同Java虚拟机上的FileChannel对象发起的对某个文件的并发访问和由非 Java 进程发起的对该文件的并发访问是一致的。

FileChannel的常用方法
int  read(ByteBuffer dst)Channel 中读取数据到 ByteBuffer
long read(ByteBuffer[] dsts)Channel 中的数据“分散”到 ByteBuffer[]
int  write(ByteBuffer src)ByteBuffer 中的数据写入到 Channel
long write(ByteBuffer[] srcs)ByteBuffer[] 中的数据“聚集”到 Channel
long position() 				 返回此通道的文件位置
FileChannel position(long p) 	 设置此通道的文件位置
long size() 					 返回此通道的文件的当前大小
FileChannel truncate(long s) 	 将此通道的文件截取为给定大小
void force(boolean metaData) 	 强制将所有对此通道的文件更新写入到存储设备中
案例一:向本地文件写入数据

需求:使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 “Hello,word!” 写入到 test1.txt 中.

 @Test
    public void test1(){
        try {
            /*1.创建一个字节输出流*/
            FileOutputStream fileOutputStream = new FileOutputStream("d://test1.txt");
            /*2.得到字节输出流对应的通道*/
            FileChannel channel = fileOutputStream.getChannel();
            /*3.分配缓冲区*/
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            /*4.再缓冲区中加入数据*/
            buffer.put("Hello,word".getBytes());
            /*5.将缓冲区切换为写出模式*/
            buffer.flip();
            /*6.使用channel将数据写出到文件*/
            channel.write(buffer);
            /*7.关闭通道*/
            channel.close();
            System.out.println("成功将数据写出到缓冲区");

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在这里插入图片描述

案例二:从本地文件中读取数据

这里我们读取刚刚写入 Hello word!!的test1.txt文件中的数据


@Test
    public void test2(){
        try {
            /*1.创建一个输入流*/
            FileInputStream fileInputStream = new FileInputStream("D://test1.txt");
            /*2.获取Channel*/
            FileChannel channel = fileInputStream.getChannel();
            /*3.创建一个缓冲区*/
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            /*4.将通道中的数据输出到缓冲区中*/
            channel.read(buffer);
            channel.close();
            buffer.flip();
           /*5.输出缓冲区中的数据*/
            String s = new String(buffer.array(), 0, buffer.remaining());
            System.out.println("文件中的数据为:"+s);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在这里插入图片描述

案例三:使用Channel与Buffer完成复制
  @Test
    public void test3() throws IOException {
        /*源文件*/
        File srcFile = new File("D://test1.txt");
        /*目标文件*/
        File destFile = new File("D://test2.txt");
        /*得到一个字节输入流*/
        FileInputStream fileInputStream = new FileInputStream(srcFile);
        /*得到一个字节输出流*/
        FileOutputStream fileOutputStream = new FileOutputStream(destFile);
        /*得到文件通道*/
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
        /*分配缓冲区*/
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(true){
            /*先清空缓冲区再将数据写入到缓冲区*/
            buffer.clear();
            /*开始读取一次数据*/
            if (fileInputStreamChannel.read(buffer)==-1){
                break;
            }
            /*将缓冲区模式切换为可读模式*/
            buffer.flip();
            /*将数据写出到文件*/
            fileOutputStreamChannel.write(buffer);
        }
        fileInputStreamChannel.close();
        fileOutputStreamChannel.close();
        System.out.println("复制数据完成");
    }

案例四:分散(Scatter)和聚集(Gather)
 @Test
    public void test4() throws IOException {
        /*1.字节输入管道*/
        FileInputStream fileInputStream = new FileInputStream("d://test1.txt");
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        /*2.字节输出流*/
        FileOutputStream fileOutputStream = new FileOutputStream("d://test3.txt");
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
        /*3.定义多个字节缓冲区*/
        ByteBuffer buffer1 = ByteBuffer.allocate(4);
        ByteBuffer buffer2 = ByteBuffer.allocate(1024);
        ByteBuffer[] buffers={buffer1,buffer2};
        /*4.从通道读取数据分散到各个缓冲区*/
        fileInputStreamChannel.read(buffers);
        for (ByteBuffer buffer : buffers) {
            buffer.flip();
            System.out.println(new String(buffer.array(),buffer.position(),buffer.remaining()));
        }
        /*5.聚集写入到通道*/
        fileOutputStreamChannel.write(buffers);
        fileInputStreamChannel.close();
        fileOutputStreamChannel.close();
        System.out.println("复制文件成功");
    }
案例五:transferFrom()和transferTo()
  @Test
    public void test5() throws IOException {
        /*1.输入流通道*/
        FileInputStream fileInputStream = new FileInputStream("d://test1.txt");
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        /*2.输出流通道*/
        FileOutputStream fileOutputStream = new FileOutputStream("d://test5.txt");
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
        /*3.复制数据*/
        /*将字节从指定的可读通道复制到此通道中*/
        fileOutputStreamChannel.transferFrom(fileInputStreamChannel, fileInputStreamChannel.position(),fileInputStreamChannel.size() );
        System.out.println(fileInputStreamChannel.size());
        /*将此通道数据复制给另外的通道*/
       // fileInputStreamChannel.transferTo(fileInputStreamChannel.position(),fileInputStreamChannel.size(),fileOutputStreamChannel);
        fileInputStreamChannel.close();
        fileOutputStreamChannel.close();
        System.out.println("完成复制!!!");
    }

Buffer与Channel的注意事项

1.ByteBuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常。[举例说明]

 @Test
    public void test6(){
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        /*存入不同数据类型的数据*/
        buffer.putInt(1);
        buffer.putChar('大');

        /*取出指定数据类型的数据*/
        buffer.flip();
        System.out.println(buffer.getInt());
        System.out.println(buffer.getChar());
    }
  1. 可以将一个普通Buffer 转成只读Buffer [举例说明]
  @Test
    public void test7(){
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        for (int i = 0; i < 20; i++) {
            buffer.putInt(i);
        }

        /*将缓冲区至为可读缓冲区*/
        buffer.flip();

        /*得到一个只读的Buffer*/
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println("readOnlyBuffer的Class类:"+readOnlyBuffer.getClass());

        /*读取*/
        while(readOnlyBuffer.hasRemaining()){
            System.out.println(readOnlyBuffer.getInt());
        }
        readOnlyBuffer.putInt(1);//加入数据会抛出异常
    }

如果在只读缓冲区中加入数据会报以下错误

在这里插入图片描述

3.NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO 来完成. [举例说明]

 @Test
    /*MapperByteBuffer可让文件直接在堆外内存拷贝,操作系统不需要拷贝一次*/
    public void test8() throws IOException {
        RandomAccessFile rw = new RandomAccessFile("d://1.txt", "rw");
        /*获取对应的通道*/
        FileChannel channel = rw.getChannel();
        MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        map.put((byte)1);

        rw.close();
        System.out.println("修改成功");
    }

4.前面我们讲的读写操作,都是通过一个Buffer 完成的,NIO 还支持 通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering 【举例说明】

选择器

概述

选择器概述

选择器(Selector) 是 SelectableChannel 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心。

在这里插入图片描述

1.Java的NIO,使用非阻塞IO的方式,可以用一个线程,处理多个客户端连接,就会使用到Selector(选择器)

2.Selector能够检测多个注册的通道是否有事件发生,(多个Channel通道以事件的方式注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理,这些就可以用一个单线程去管理多个通道,也就是管理多个连接与请求。

3.只有在连接/通道 真正有读写事件发生的时候,才会进行读写,就大大减小了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程

4.避免了多线程直接的上下文切换导致的开销

选择器特点再说明

1.特点再说明: Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。

2.当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。

3.线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。

4.由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。

5.一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

在这里插入图片描述

Selector类相关方法

概述

public abstract class Selector implements Closeable { 
 public static Selector open();//得到一个选择器对象 
 
 public int select(long timeout);//监控所有注册的通道,当其中有 IO 操作可以进行时,将 对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间 
 
 public Set<SelectionKey> selectedKeys();//从内部集合中得到所有的 SelectionKey 
 }

在这里插入图片描述

注意事项

1.NIO中的 ServerSocketChannel功能类似ServerSocket,SocketChannel功能类似Socket

2.Selector 相关方法说明

selector.select()//阻塞

selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回

selector.wakeup();//唤醒

selector selector.selectNow();//不阻塞,立马返还

NIO 非阻塞 网络编程原理剖析

1.当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel

2.Selector进行监听,select()方法,返回有事件发生的通道的个数

2.将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel

3.注册后返回一个 SelectionKey, 会和该Selector 关联(集合)

4.进一步得到各个 SelectionKey (有事件发生)

5.在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()

6.可以通过 得到的 channel ,

7.完成业务处理 代码撑腰。。。

在这里插入图片描述

选择器的应用

创建 Selector:通过调用 Selector.open() 方法创建一个 Selector。

Selector selector = Selector.open();

绑定选择器及其对应的事件

 public void test9(){
        try {
            /*1.获取通道*/
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            /*2.切换非阻塞模式*/
            serverSocketChannel.configureBlocking(false);
            /*3.绑定连接*/
            serverSocketChannel.bind(new InetSocketAddress(9999));
            /*4.获取选择器*/
            Selector selector = Selector.open();
            /*5.将通道注册到选择器上,并指定监听事件*/
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT| SelectionKey.OP_WRITE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。可以监听的事件类型(可使用 SelectionKey 的四个常量表示):

读 : SelectionKey.OP_READ (1)
写 : SelectionKey.OP_WRITE (4)
连接 : SelectionKey.OP_CONNECT (8)
接收 : SelectionKey.OP_ACCEPT (16)
若注册时不止监听一个事件,则可以使用“位或”操作符连接。

int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE 

Selector示意图与特点说明

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值