一个小时就能理解Java的NIO必须掌握这三大要素!
同步与阻塞
同步和异步是针对应用程序和内核的交互而言的。
- 同步:执行一个操作之后,进程触发IO操作并等待(阻塞)或者轮询的去查看IO的操作(非阻塞)是否完成,等待结果,然后才继续执行后续的操作。
- 异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
- 非阻塞是针对于进程在访问数据的时候,根据IO口的状态返回不同的状态值。阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
- 阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
- 非阻塞:进程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。(轮询)
- 同步异步是结果,阻塞非阻塞是手段。
Java IO的各种流是 同步阻塞 的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 当然,他还有一个更加重要的特性是,多路复用IO。
Java NIO的 同步非阻塞 模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。
之前使用futrueModel的就是类似异步IO模型。 异步一定是非阻塞的。
概述
Java的NIO有三个核心部分:Channels、Buffers、Selectors
Channel 和 Buffer
Channel有四个类型:
小编分类整理了许多java进阶学习材料和BAT面试题,需要资料的请转发此文章后再私聊小编回复【java】就能领取2019年java架构师进阶学习资料和BAT面试题。
可以将一个Channel看作一个流,而buffer和channel联合起来就是一个带有缓冲区域的流。和stream不同的是,stream的方向是单向的,但是buffer和channel之间的数据传输是双向的,数据既可以从channel到buffer,也可以从buffer到channel。
Selector
Selector是一个管理器,可以管理多个channel。在线程中使用Selector,将Channel注册到Selector中,在channel中有事件就绪,就会将调用select方法,获取事件,响应事件。
Channel
Channel 是一个接口,其常用的实现类有下面四个:
一个Channel的使用实例。
package com.xzj; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; // @ author :zjxu time:2019/1/8 public class Main { final static String PATH = "/Users/thisxzj/IDEAProject/Revise/NIO/src/index.txt"; public static void main(String[] args) throws IOException { RandomAccessFile file = new RandomAccessFile(PATH, "rw"); FileChannel fileChannel = file.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(64); int length = fileChannel.read(byteBuffer); if (length != -1) { System.out.println("length = " + length ); byteBuffer.flip(); //反转buffer while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } //byteBuffer.rewind(); //while (byteBuffer.hasRemaining()) { // System.out.print((char) byteBuffer.get()); //} byteBuffer.clear(); } fileChannel.close(); } }
运行结果:
length = 12
Hello World!
Buffer
Buffer是一个抽象类,他有有八个实现类型,对应八种基本数据类型除了布尔型的七种,以及一个映射类型:
buffer的基本用法
- 写入数据到Buffer
- 调用 flip() 方法
- 从Buffer中读取数据
- 调用 clear() 方法或者 compact() 方法
buffer的内部
buffer内部是一个对应类型的数组,这个数字有四个index,这四个index在数据的存取过程中有自己作用。属性解释:
- capacity:缓冲区数字的总长度。
- pasition:下一个要操作的数据元素的位置。
- limit:缓冲区不可操作的下一个位置的位置。
- mark:记录position前一个位置,default:-1(也就是不存在)。
使用方法:
- 创建方法:ByteBuffer.allocate(n) //创建一个长度为n的Byte的缓冲区。这个时候的 capacity 和 limit的大小都是数组的长度;position的大小是0,数组的首端。
- 在写入了数据之后,position会变为数组没有存储数据的位置的一个位置。
- 使用byteBuffer.flip方法,limit = position,position = 0,然后就可以正确的读取这鞋数据,并且将数据发送出去。
- 然后使用 byteBuffer.clear方法,将会到刚刚创建的状态。
- 关于 mark 方法 ,使用mark方法的时候,mark会记录position - 1 的数据大小。当再次使用reset的时候,position将不会还原,会回到mark的值。
几个方法:
public Buffer flip() { limit = position; position = 0; mark = -1; return this; } ······ public Buffer clear() { position = 0; limit = capacity; mark = -1; return this; } ······ public Buffer rewind() { position = 0; mark = -1; return this; } ······ public final Buffer mark() { mark = position; return this; } ······ public Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
flip方法
反转buffer,反转缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。通常情况下,在从写入状态转向读取状态的时候调用flip方法。
clear、compact方法
清除方法,清除整个缓冲区,相当于是再次初始化。和这个类似的还有一个compact方法。clear的作用是将全部缓冲区域清除,如果缓冲区中的数据全部使用结束,那么使用clear是没有问题的。
如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么就使用compact()方法。compact()方法将所有未读的数据拷贝搬移Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。也就是将没有使用过的数据搬移到初始端,然后,在原先数据的后面接着写入新的数据。
rewind 方法
无论是在读还是写的模式下,都从头开始。如果是读的模式,使用这个方法,写入新的数据将会覆盖原来数据的。如果是读取的话,将会重复的将原先读过的数据在读一遍。
如将channel中的注释打开,那么执行的结果就是:
length = 12
Hello World!Hello World
这就体现了在读取的时候rewind的作用。
mark()与reset()方法
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。
例如,先mark一下,然后经过读或者是写,之后,使用reset可以将position返回到使用mark的时候的那个mark值。但是flip、clear、compact、rewind、reset都会将mark标记回归负一,也就是取消掉。在使用reset的时候,必须要有一个mark,否则会exception。
buffer的使用
创建
使用Buffer抽象类的实现类的静态方法来创建,例如:
//创建一个对应的Buffer的对象,其最大容量为128。 XXXBuffer xxxBuffer = XXXBuffer.allocate(128);
写数据
从channel中:
int readLength = inChannel.read(xxxBuffer);
使用channel的read方法,将channel中的数据传入xxxBuffer中。
使用put方法:
xxxBuffer.put(new byte[]{' ', 'x', ' ', 'z', ' ', 'j', ' ', '!'});
使用buffer对象的put方法,将put方法的参数,传入的到buffer中。这个参数可以是这个buffer的类型的值、数组。还可以是另一个同类型的buffer,这个参数的本质也是一个数组。
读取数据
从Buffer读取数据到Channel:
int writeLength = inChannel.write(xxxBuffer);
使用get方法获取buffer中的数据:
xxxBuffer.get(); //获取的是一个xxx类型的数据
其他
可以使用equare 和 compareTo来比较两个buffer。
多buffer操作
使用多个buffer,对应一个channel。
scatter
使用buffer的数组从channel中获取信息的时候,将会按照数组中buffer的顺序,将channel中的信息,保存在buffer中。
package com.xzj; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; // @ author :zjxu time:2019/1/8 public class Main { final static String PATH = "/Users/thisxzj/IDEAProject/Revise/NIO/src/index.txt"; public static void main(String[] args) throws IOException { RandomAccessFile file = new RandomAccessFile(PATH, "rw"); FileChannel fileChannel = file.getChannel(); ByteBuffer byteBuffer1 = ByteBuffer.allocate(10); ByteBuffer byteBuffer2 = ByteBuffer.allocate(100); ByteBuffer[] byteBuffer = new ByteBuffer[]{byteBuffer1, byteBuffer2}; long length = fileChannel.read(byteBuffer); if (length != -1) { System.out.println("length = " + length); byteBuffer2.flip(); //反转buffer while (byteBuffer2.hasRemaining()) { System.out.print((char) byteBuffer2.get() ); } byteBuffer2.clear(); } fileChannel.close(); } }
index.txt中保存的文本:
Hello World!Hello World!
程序的输出:
length = 24
d!Hello World!
很明显,第一个长度为10的数组中,保存了前面十个字符。后面长度为100的保存了后面的十四位。在移动下一个buffer前,必须填满当前的buffer。
gather
同样的,channel在获取数字的时候,可以有这样的数组的机制。但是需要注意的是,只有position和limit之间的数字会被填充进去。所以和前面的一样,需要使用filp更改来将数据刷新进去。在满的时候,或者是将队尾的空格输入进去的时候,使用rewind。
package com.xzj; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; // @ author :zjxu time:2019/1/8 public class Main { final static String PATH = "/Users/thisxzj/IDEAProject/Revise/NIO/src/index.txt"; public static void main(String[] args) throws IOException { RandomAccessFile file = new RandomAccessFile(PATH, "rw"); FileChannel fileChannel = file.getChannel(); ByteBuffer byteBuffer1 = ByteBuffer.allocate(10); ByteBuffer byteBuffer2 = ByteBuffer.allocate(10); byteBuffer1.put("0123456789".getBytes()); byteBuffer1.rewind(); byteBuffer2.put("ABCDEFGHIJ".getBytes()); byteBuffer2.rewind(); ByteBuffer[] byteBuffer = new ByteBuffer[]{byteBuffer1, byteBuffer2}; long length = fileChannel.write(byteBuffer); System.out.println(length); fileChannel.close(); } }
index.txt中的文本:
0123456789ABCDEFGHIJ
控制台信息:
通道间通信
transferFrom
将from中文本信息复制到to中,如果to中文本信息长度比from长,多余的一部分保留。
package com.xzj; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; // @ author :zjxu time:2019/1/8 public class Main { final static String FROM = "/Users/thisxzj/IDEAProject/Revise/NIO/src/fromIndex.txt"; final static String TO = "/Users/thisxzj/IDEAProject/Revise/NIO/src/toIndex.txt"; public static void main(String[] args) throws IOException { RandomAccessFile fromFile = new RandomAccessFile(FROM, "rw"); RandomAccessFile toFile = new RandomAccessFile(TO, "rw"); FileChannel fromChannel = fromFile.getChannel(); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); toChannel.transferFrom(fromChannel, position, count); fromChannel.close(); toChannel.close(); } }
运行结果:
fromIndex:abcdbfghijkrmnopqrstuvwxyz toIndex:1234567890123456789012345678901234567890 运行之后,from中文本不变,to中的文本变化如下: toIndex:abcdbfghijkrmnopqrstuvwxyz78901234567890
transferTo
将上面的程序的第21行改为:
fromChannel.transferTo(position, count, toChannel);
其余的逻辑,方法都不变。
SelectableChannel
和selector配套使用的是selectable接口的实现类,通常是网络接口。所以,NIO是和网络紧密相关的一个IO机制。
- 有关UDP协议的:DatagramChannel。
- 有关SCTP协议的:SctpChannel、SctpMultiChannel、SctpServerChannel。
- 有关TCP协议的:ServerSocketChannel、SocketChannel。
- 有关管道的:SinkChannel、SourceChannel这两个抽象类定义在java.nio.channels.Pipe类中。
- Socket和ServerSocket是位于java.net下的两个类。
- SocketChannel和ServerSocketChannel是位于java.nio.channels下的两个类。
连接关系:
- 服务器必须先建立ServerSocket或者ServerSocketChannel 来等待客户端的连接。
- 客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接。
- 服务器接受到客户端的连接,再生成一个Socket或者SocketChannel与此客户端通信。
SocketChannel
打开方法
//客户端 SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));
连接方法
//客户端 socketChannel.configureBlocking(false); //非阻塞模式 socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));
写入方法
//客户端 ByteBuffer writeBuffer = ByteBuffer.allocate(64); writeBuffer.clear(); writeBuffer.put((new Date().toString()).getBytes()); writeBuffer = writeBuffer.flip(); socketChannel.write(writeBuffer);
读取方法
//客户端 readBuff.clear(); int length = socketChannel.read(readBuff); readBuff.flip(); System.out.println("received: " + new String(readBuff.array()).substring(0, length));
关闭方法
//客户端 socketChannel.close();
ServerSocketChannel
打开方法
//服务端 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000)); ssc.configureBlocking(false);
连接方法
//服务端 ssc.configureBlocking(false); //非阻塞模式 ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000));
监听连接
通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。
通常不会仅仅只监听一个连接,在while循环中调用 accept()方法。如下面的例子:
while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }
当然,也可以在while循环中使用除了true以外的其它退出准则。
读取方法
//使用一个新建的Socket连接,在这个连接上使用read和write readBuff.clear(); int length = socketChannel.read(readBuff); readBuff.flip(); System.out.println("received: " + new String(readBuff.array()).substring(0, length));
关闭方法
//服务端 serverSocketChannel.close();
TCP的使用示例
WebServer
package com.xzj; // @ author :zjxu time:2019/1/9 import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class WebServer { public static void main(String[] args) throws IOException { //创建一个channel并且设定为非阻塞模式 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8000)); serverSocketChannel.configureBlocking(false); //创建selector Selector selector = Selector.open(); //将channel和selector关联起来,并且指定感兴趣的事件是 Accept serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //创建读写buffer ByteBuffer readBuff = ByteBuffer.allocate(1024); ByteBuffer writeBuff = ByteBuffer.allocate(128); writeBuff.put("enter:".getBytes()); writeBuff.flip(); while (true) { int nReady = selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); while (it.hasNext()) { //轮询 SelectionKey key = it.next(); it.remove(); if (key.isAcceptable()) { // 创建新的连接,并且把连接注册到selector上,而且 // 声明这个channel只对读操作感兴趣 SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); readBuff.clear(); int length = socketChannel.read(readBuff); readBuff.flip(); System.out.println("received: " + new String(readBuff.array()).substring(0, length-1)); key.interestOps(SelectionKey.OP_WRITE); } else if (key.isWritable()) { writeBuff.rewind(); SocketChannel socketChannel = (SocketChannel) key.channel(); socketChannel.write(writeBuff); key.interestOps(SelectionKey.OP_READ); } } } } }
WebClient
package com.xzj; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Date; // @ author :zjxu time:2019/1/9 public class WebClient { public static void main(String[] args) throws IOException { try { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000)); //创建读写buffer ByteBuffer writeBuffer = ByteBuffer.allocate(64); ByteBuffer readBuffer = ByteBuffer.allocate(32); while (true) { writeBuffer.clear(); writeBuffer.put((new Date().toString()).getBytes()); writeBuffer.flip(); socketChannel.write(writeBuffer); readBuffer.clear(); socketChannel.read(readBuffer); } } catch (IOException e) { e.printStackTrace(); } } }
DatagramChannel
打开方法
DatagramChannel channel = DatagramChannel.open();
连接方法
channel.socket().bind(new InetSocketAddress(8000));
收发方法
ByteBuffer sendBuffer = ByteBuffer.allocate(64); ByteBuffer receiveBuffer = ByteBuffer.allocate(64); //接收方法 receiveBuffer.clear(); channel.receive(receiveBuffer); String string = new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date()); //发送方法 sendBuffer.clear(); sendBuffer.put(string.getBytes()); sendBuffer.flip(); channel.send(sendBuffer,new InetSocketAddress(8001));
可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。
这里有个例子:
channel.connect(``new` `InetSocketAddress(``"jenkov.com"``, ``80``));`
当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证。这里有几个例子:
int bytesRead = channel.read(buf); int bytesWrite = channel.write(buf);
UDP的使用示例
package com.xzj; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Date; import java.util.Iterator; import java.util.Scanner; // @ author :zjxu time:2019/1/9 public class DatagramTest { public void send() throws IOException { DatagramChannel dChannel = DatagramChannel.open(); dChannel.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocate(1024); Scanner scanner = new Scanner(System.in); while(scanner.hasNext()){ String string = scanner.next(); buffer.put((new Date().toString()+">>"+string).getBytes()); buffer.flip(); dChannel.send(buffer, new InetSocketAddress("127.0.0.1", 8989)); buffer.clear(); } dChannel.close(); } public void receive() throws IOException { DatagramChannel datagramChannel = DatagramChannel.open(); datagramChannel.configureBlocking(false); datagramChannel.bind(new InetSocketAddress(8989)); Selector selector = Selector.open(); datagramChannel.register(selector, SelectionKey.OP_READ); while(selector.select()>0){ Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next(); if (selectionKey.isReadable()) { ByteBuffer buffer = ByteBuffer.allocate(1024); datagramChannel.receive(buffer); buffer.flip(); System.out.println(new String(buffer.array(),0,buffer.limit())); buffer.clear(); } } iterator.remove(); } } }
Pipe
JavaNIO中的pipe是两个 线程之间 的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
创建管道
Pipe pipe = Pipe.open();
写入
SinkChannel sinkChannel = pipe.sink(); ByteBuffer byteBuffer = ByteBuffer.allocate(64); String string = new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date()); byteBuffer.clear(); byteBuffer.put(string.getBytes()); byteBuffer.flip(); sinkChannel.write(byteBuffer);
读取
SourceChannel sourceChannel = pipe.source(); ByteBuffer readBuffer = ByteBuffer.allocate(64); readBuffer.clear(); int length = sourceChannel.read(readBuffer); System.out.println(new String(readBuffer.array()).substring(0, length - 1)); sourceChannel.close();
Pipe使用示例
package com.xzj; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Pipe; import java.nio.channels.Pipe.SinkChannel; import java.nio.channels.Pipe.SourceChannel; import java.text.SimpleDateFormat; import java.util.Date; // @ author :zjxu time:2019/1/9 public class Test { public static void main(String[] args) throws IOException { Pipe pipe = Pipe.open(); SinkChannel sinkChannel = pipe.sink(); ByteBuffer writeBuffer = ByteBuffer.allocate(64); String string = new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date()) + " "; writeBuffer.clear(); writeBuffer.put(string.getBytes()); writeBuffer.flip(); sinkChannel.write(writeBuffer); sinkChannel.close(); SourceChannel sourceChannel = pipe.source(); ByteBuffer readBuffer = ByteBuffer.allocate(64); readBuffer.clear(); int length = sourceChannel.read(readBuffer); System.out.println(new String(readBuffer.array()).substring(0, length - 1)); sourceChannel.close(); } }
运行结果:
Selector
使用scelector的好处是,只需要更少的线程来维护通道。,可以使用以恶搞线程处理所有的通道。
创建方法
Selector selector = Selector.open(); //selector.isOpen();判断是否打开 //selector.close();关闭
注册通道
channel.configureBlocking(false); SelectionKey readKey = channel.register(selector,SelectionKey.OP_READ);
第二个参数,表示监听的动作:
有四个类型:
Key
使用key常量将selector和channel中的动作关联起来,并返回一个SelectionKey对象。只要是channel向selector中注册了事件,selector就会跟踪检测这个事件是否发生。这些的key对象就是检测这些事件的句柄。
获取keys
//所有注册到selector的key Set<SelectionKey> keys = selector.keys(); //相关事件被捕获的key Set<SelectionKey> selectedKeys = selector.selectedKeys();
如果关闭了与SelectionKey对象关联的Channel对象,或者调用了SelectionKey对象的cancel方法,这个SelectionKey对象就会被加入到cancelled-keys集合中,表示这个SelectionKey对象已经被取消。
cancelled-keys没有对应的方法被获取。
interest集合
int interestSet = key.interestOps(); boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = (interestSet & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT; boolean isInterestedInRead = (interestSet & SelectionKey.OP_READ) == SelectionKey.OP_READ; boolean isInterestedInWrite = (interestSet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
用来检测对那些事件感兴趣。其中:
使用interestOps();方法获取的是一个多位二进制数,通过逻辑操作,可以判断是不是在key上有对应的操作。
ready集合
int readySet = key.readyOps(); boolean isAcceptable = (readySet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean isConnectable = (readySet & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT; boolean isReadable = (readySet & SelectionKey.OP_READ) == SelectionKey.OP_READ; boolean isWritable = (readySet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
和上面的获取机制一模一样。此外还可以使用直接获取的方式,获取ready状态。
key.isAcceptable(); key.isConnectable(); key.isReadable(); key.isWritable();
附加对象
通过key生成的channel和seletor:
Channel keyChannel = key.channel(); Selector keySelector = key.selector();
将一个对象添加到key上:
StringBuilder stringBuilder = new StringBuilder("xzj"); //添加上去 key.attach(stringBuilder); //从key获取 StringBuilder getter = (StringBuilder) key.attachment();
select()
向一个selector中注册多个channel之后,可以使用selector的select方法,该方法的返回值是一个int型,表示的是通道就绪的数量。使用的select()方法是阻塞等待至少一个通道准备就绪,其中可以向其中添加一个最长等待时间,也可以使用selectNow()来表示立即返回。
如下:
selector.select(); selector.select(1000); selector.selectNow();
遍历
Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator selectedKeysIterator = selectedKeys.iterator(); while (selectedKeysIterator.hasNext()) { SelectionKey key = (SelectionKey) selectedKeysIterator.next(); if (key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel } else if (key.isConnectable()) { // a connection was established with a remote server } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } //将刚刚访问过的元素删除 selectedKeysIterator.remove(); }
weakUp方法
在某一个线程在使用select()方法被阻塞,而无法返回的时候,在同一个select对象上使用weakUp()方法,会使阻塞的select方法返回。