参考链接:
http://www.iteye.com/magazines/132-Java-NIO
https://www.cnblogs.com/xiaoxi/p/6576588.html
http://www.importnew.com/19816.html
NIO概述:
New IO。首先它是一种IO。IO的主要作用是什么呢?1、读取写入文件 2、Socket信息交互。没错NIO的主要作用也是这个。NIO的实现是面向缓冲区的,块的形式,所以NIO的效率要比IO高很多。
NIO中的一些重要的概念:
通道Channel
缓冲区Buffer
选择器Selector
Channel类似NIO操作的进出口,Buffer是进行操作的中间介质
多个IO操作同时进行的时候,Selector可以使用一个线程自动在适合的时间让IO操作进行对应的执行,它是一个调配者
Ok,那么现在开始介绍Buffer
缓冲区是特定基本类型元素的线性有限序列。对于每个非boolean类型,都有一个子类与其对应。
缓冲区的基本属性包括:容量、限制、位置
容量capacity:缓冲区所能包含的最多元素数量。容量不能为负且无法更改。可以将缓冲区理解成为一维数组
限制limit:第一个不能读取和写入的元素的索引,位置不能为负且不能大于容量,针对缓冲区操作的红线。
位置position:下一个要读取和写入的元素的索引,位置不能为负且不能大于其限制
操作:
1、传输数据
相对操作读取或写入一个或多个元素,从当前位置开始,然后将位置增加所传输的元素数目。如果请求的传输超过限制,获取操作则抛出BufferOverflowException,放置操作抛出BufferUnderflowException。这两种情况下都没有数据被传输,也就是类似先检查再操作。
绝对操作采用显式元素索引,该操作不影响位置。如果索引超出限制,则抛出IndexOutBoundsException
当然,通过适当Channel的IO操作也可以将数据传输到缓冲区或者从缓冲区传出数据
2、标记和重置
缓冲区的标记是一个索引。
mark方法在此位置设置标记。
reset方法将缓冲区的位置重置为该标记的位置
定义标记不能定义为负数,并且不能让它大于位置。如果将位置position或者限制limit调整为小于该标记的值的时候,自动丢弃该标记。未有标记调用reset方法将抛异常InvalidMarkException
0 <= 标记 <= 位置 <= 限制 <= 容量,位置等从0开始
3、清除、反转和重绕
clear():使缓冲区为一系列的新的通道读取或相对放置操作做准备:将limit设置为capacity,将position设置为0
flip(): 使缓冲区为一系列新通道的写入或者相对获取操作作好准备:将limit设置为position,将position设置为0
rewind(): 使缓冲区为重新读取已包含的数据作好准备:limit不变,position设置为0
4、缓冲区可以设置为只读缓冲区
主要方法:
abstract Object array() 返回此缓冲区的底层实现数组
abstract int arrayOffset() 返回缓冲区底层实现数组中第一个缓冲区元素的偏移量
int capacity() 返回此缓冲区的容量
Buffer clear() 清除缓冲区
Buffer flip() 反转缓冲区
abstract boolean hasArray() 告知此缓冲区是否具有可访问的底层实现数组
boolean hasRemaining() 告知在当前位置和限制之间是否有元素
abstract boolean isDirect() 告知此缓冲区是否为直接缓冲区
abstract boolean isReadOnly() 告知此缓冲区是否为只读缓冲区
int limit() 返回此缓冲区的限制
Buffer limit(int newLimit) 设置此缓冲区的限制
Buffer mark() 在此缓冲区的位置设置标记
int position() 返回此缓冲区的位置
Buffer position(int newPosition) 设置此缓冲区的位置
int remaining() 返回当前位置与限制之间的元素数
Buffer reset() 将此缓冲区的位置重置为标记的位置
Buffer rewind() 重绕此缓冲区
缓冲区实现类ByteBuffer
概述:
字节缓冲区
针对字节缓冲区定义了如下六类操作
1、读写单个字节的绝对和相对get和put方法
abstract byte get() 相对get方法
ByteBuffer put(byte b) 相对put方法
abstract byte get(int ndex)
ByteBuffer put(int index, byte b)
2、将此缓冲区的连续字节序列传输到其他数组中的批量get方法
public ByteBuffer get(byte[] dst, int offset,
int length)
批量相对get方法。将缓冲区的字节传输到给定的目标数组中。如果此缓冲区中剩余的字节少于请求需要的字节(remaining() <
length),则不传输字节且抛出BufferUnderflowException。相当于:
for(int i = offset, i < offset + length;
i++) {dst[i] = src.get();}
public ByteBuffer get(byte[] dst) 相对批量get方法,相当于src.get(a, 0,
a.length)
3、将byte数组或者其他字节缓冲区中的连续字节序列传输到此缓冲区的相对批量put方法
public ByteBuffer put(byte[] src, int offset,
int length) 相对批量put方法,如果数组中赋值的字节多余缓冲区中剩余的字节,则不传输字节且将抛出BufferOverflowException。相当于:
for(int i = off, i < off + len; i++)
{dst.put(a[i]};
public final ByteBuffer put(byte[] src)
4、读写其他基本类型,并按照特定的字节顺序在字节序列之间转换这些值的绝对和相对get和put方法
public abstract char getChar() 用于读取char值的相对get方法。注意:char占两个字节
public abstract ByteBuffer putChar(char
value)
public abstract char getChar(int index) 读取给定索引处的两个字节,兵根据当前的字节顺序将它们组成char值
public abstract ByteBuffer putChar(int index,
char value)
short、int、long、double、float、
5、创建视图缓冲区的方法,这些方法允许将字节缓冲区视为包含其他基本类型值的缓冲区
public abstract CharBuffer
asCharBuffer() 创建此字节缓冲区的字符作为char缓冲区
新缓冲区的内容从此缓冲区的当前位置开始,可见性与原缓冲区一致,两个缓冲区的位置、界限和标记值都是相互独立的。新缓冲区的位置将为0,容量为此缓冲区剩余字节数的1/2,其标记是不确定的
short int long float double...
byte (1) char(2) short(2) int(4)
long(8) float(4) double(8)
6、对字节缓冲区进行compacting、duplicating和slicing的方法
public abstract ByteBuffer compact() 压缩缓冲区
将缓冲区的当前位置和界限之间的字节复制到缓冲区的开始处。n = position -
limit,然后将位置设置为n+1,将limit设置为capacity。有标记则将标记丢弃。
从缓冲区写入数据之后调用此方法,防止写入的不完整
public abstract ByteBuffer duplicate() 创建共享此缓冲区内容的新的字节缓冲区
新缓冲区的内容为此缓冲区的内容。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然。这两个缓冲区的位置、界限和标记值都是相互独立的。新缓冲区的容量、界限、位置和标记值将与此缓冲区相同,可读与是否直接属性与此缓冲区对应
public abstract ByteBuffer slice() 创建新的字节缓冲区,其内容是此缓冲区的共享子序列
新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然
新缓冲区的位置将为0,其容量和界限将为此缓冲区中剩余的字节数量,其标记是不确定的。刻度与是否直接与此缓冲区对应
7、创建字节缓冲区通过allocation方法创建,此方法为缓冲区的内容分配空间,或通过warpping方法将现有的byte数组包装到缓冲区中来创建
public static ByteBuffer allocate(int
capacity)
public static ByteBuffer wrap(byte array, int
offset, int length) 将byte数组包装到缓冲区中
新的缓冲区将由给定的byte数组支持;也就是说缓冲区修改将导致数组修改,反之亦然。新缓冲区的容量将为array.length。位置为offset,界限为offset + length
public static ByteBuffer wrap(byte[] array) 将byte数组包装到缓冲区中。不过新缓冲区的位置为0,界限为array.length
8、直接与非直接缓冲区
直接的字节缓冲区,则Java虚拟机会尽量在此缓冲区上执行本机IO操作。
直接字节缓冲区通过allocateDirect创建。此方法返回的缓冲区进行分配和取消分配的成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外
直接字节缓冲区还可以通过mapping将文件区域直接映射到内存来创建,fileChannel的方法。
字节缓冲区是直接缓冲区还是非直接缓冲区可以通过调用其isDirect方法来确定
package kunpu.nio; import sun.nio.ch.DirectBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays; /**
* @author zhen
*/
public class BufferOperator { public static void main(String[] args) {
//创建字节缓冲区
ByteBuffer buffer = ByteBuffer.allocate(100);
ByteBuffer buffer1 = ByteBuffer.wrap(new byte[100]);
ByteBuffer buffer2 = ByteBuffer.wrap(new byte[100], 20, 8); //输出缓冲区的属性
System.out.println("java.nio.HeapByteBuffer[pos=" + buffer.position() + " limit=" + buffer.limit() + " cap=" + buffer.capacity() + "]");
System.out.println(buffer1);
System.out.println(buffer2); //读写单字节
byte b = 12;
buffer.put(b);
System.out.println(buffer);
//读取的是position位置的元素
byte x = buffer.get();
System.out.println(x);
System.out.println(buffer);
buffer.put(2, (byte)2);
System.out.println(buffer);
System.out.println(buffer.get(2)); //批量put
buffer.put(new byte[]{(byte)3, (byte)4});
System.out.println(buffer);
System.out.println(buffer.get(2));
buffer.put(new byte[]{(byte)3, (byte)4, (byte)5}, 0, 1); ByteBuffer buffer3 = ByteBuffer.allocate(100);
byte[] values = {(byte)1, (byte)2, (byte)3, (byte)4};
buffer3.put(values); //输出一些属性
System.out.println("array= " + Arrays.toString(buffer3.array()));
System.out.println("arrayOffSet= " +buffer3.arrayOffset());
System.out.println("hasArray= " + buffer3.hasArray());
System.out.println("hasRemaining= " + buffer3.hasRemaining());
System.out.println("isDirect= " + buffer3.isDirect());
System.out.println("isReadOnly= " + buffer3.isReadOnly());
System.out.println("remaining= " + buffer3.remaining()); //flip等操作
System.out.println(buffer3);
buffer3.mark();
buffer3.put((byte)5);
System.out.println(buffer3);
// position = mark ,不清除元素
buffer3.reset();
System.out.println(buffer3);
System.out.println(buffer3.get(4));
//position=0, 不清除缓冲区
buffer3.clear();
System.out.println(buffer3);
System.out.println(buffer3.get(4));
//limit = position 并且position = 0
buffer3.flip();
System.out.println(buffer3);
//System.out.println(buffer3.get(4)); index > limit throws IndexOunBoundsException // position >=0 并且 position <= limit
buffer3.limit(100);
buffer3.position(5);
System.out.println(buffer3.get(4));
System.out.println(buffer3);
// position = 0 并且清除mark
buffer3.rewind();
System.out.println(buffer3); // 读取其他类型
System.out.println(buffer3.getChar());
System.out.println(buffer3);
buffer3.putChar('S');
System.out.println(buffer3 + "\n=================="); //获得视图缓冲区, 是将缓冲区剩下的空白地段用作视图缓冲区的底层数组
CharBuffer charBuffer = buffer3.asCharBuffer();
System.out.println(charBuffer.position() + ", " + charBuffer.limit() + ", " + charBuffer.capacity()); //compacting、 duplicating、 slicing
ByteBuffer buffer4 = ByteBuffer.allocate(100);
buffer4.put(new byte[]{(byte)6, (byte)7, (byte)8, (byte)9});
System.out.println(buffer4);
buffer4.flip();
System.out.println(buffer4.get() + ", " + buffer4.get());
System.out.println(buffer4);
//将position和limit之间元素依次放在0位置开始,position=原来的limit-原来的position, limit= capacity, 有标记丢弃标记
buffer4.compact();
System.out.println(buffer4);
System.out.println(buffer4.get() + ", " + buffer4.get());
System.out.println(buffer4);
//初始化容量位置界限与原缓冲区相同,不过两缓冲区其容量界限位置是相对独立的,但是内容是共享的
ByteBuffer buffer5 = buffer4.duplicate();
System.out.println(buffer5);
buffer4.put((byte)3);
System.out.println(buffer5.get(4));
System.out.println(buffer4);
//初始化位置为0,容量和界限为原来缓冲区剩余字节数量。内容此缓冲区的当前位置开始,内容共享,属性独立
ByteBuffer buffer6 = buffer4.slice();
System.out.println(buffer6);
buffer4.put((byte)7);
System.out.println(buffer6);
System.out.println(buffer6.get()); //直接缓冲区
DirectBuffer directBuffer = (DirectBuffer) ByteBuffer.allocateDirect(100); } }
Channel
用于I/O操作的连接,通道表示到实体,如硬件设备、文件、网络套接字或可以执行一个或多个不通I/O操作的程序组件的开放的连接
两个方法:
public void close() 关闭
public boolean isOpen()
我们首先是学习针对文件操作,所以FileChannel
概述:
用于读取、写入、映射和操作文件的通道
文件通道在其文件中有一个当前position,可对其进行查询和修改。文件本身包含一个可读写的长度可变的字节数组,可以查询该文件的当前大小。除了字节通道中常见的读取、写入和关闭操作外,此类还定义了下列特定于文件的操作
1、对绝对位置的字节进行读取或写入
2、将文件中的某个区域直接映射到内存中;对于较大的文件,通常比调用普通的read或write更为高效
3、强制对底层存储设备进行文件的更新,确保在系统崩溃时不丢失数据
4、以一种可以被很多操作系统优化为直接向文件存储发送或从中获取高速传输方法,将字节从文件传输到某个其他通道中
5、可以锁定某个文件区域,以防止其他程序对其进行访问
此类没有定义打开现有文件或创建新文件的方法,以后的版本中可能添加这些方法。目前,从FileInputStream、FileOutputStream或RandomAccessFile对象获取文件通道,方法是调用该对象的getChannel方法。
方法:
abstract void force(boolean metaData) 强制将所有对此通道的文件更新写入包含该文件的存储设备中
FileLock lock() 获取对此通道的文件的独占锁定
abstract FileLock lock(long position, long size, boolean shared) 获取对此通道的文件给定区域上的锁定
abstract MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) 将此通道的文件区域直接映射到内存中
abstract long position() 获取此通道的文件位置 (文件位置的概念,就是文件开始的字节数,写入和读取的时候都是从这个位置开始的)
abstract FileChannel position(long newPosition) 设置此通道的文件位置
long read(ByteBuffer[] dst) 将字节序列从此通道读入给定的缓冲区
abstract int read(ByteBuffer[] dst, int offset, int length) 从给定的文件位置开始,从此通道读取字节序列,并写入给定的缓冲区
abstract long size()
abstract long transferFrom(ReadableByteChannel src, long position, long count) 将字节从给定的可读取字节通道传输到此通道的文件中
abstract transferTo (long position, long count, WritableByteChannel target) 将字节从此通道的文件传输到给定的可写入字节通道
abstract FileChannel truncate(long size) 将此通道的文件截取为给定大小
FileLock tryLock() 视图获取对此通道文件的独占锁定
abstract FileLock tryLock(long position, long size, boolean shared)视图获取对此通道的文件的给定区域的锁定
abstract int write(ByteBuffer src)将字节序列从给定的缓冲区写入此通道
long write(ByteBuffer[] srcs) 将字节序列从给定的缓冲区写入此通道
abstract long write(ByteBuffer[] srcs, int offset, int length)将字节序列从给定缓冲区的子序列写入此通道
abstract int write(ByteBuffer src, long position) 从给定的位置开始,将字节序列从给定缓冲区写入此通道
package kunpu.nio; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel; /**
* @author zhen
*/
public class ChannelOperator { public static void main(String[] args) throws IOException{
FileChannel channel1 = new FileInputStream("src/java/kunpu/nio/text1.txt").getChannel();
FileChannel channel2 = new RandomAccessFile("src/java/kunpu/nio/text2.txt", "rw").getChannel();
FileChannel channel3 = new FileOutputStream("src/java/kunpu/nio/text3.txt").getChannel();
FileChannel channel4 = new FileOutputStream("src/java/kunpu/nio/text4.txt").getChannel(); System.out.println("isOpen= " + channel1.isOpen());
System.out.println("size= " + channel1.size());
System.out.println("position= " + channel1.position()); //读写
ByteBuffer buffer = ByteBuffer.wrap("你是一个好人".getBytes());
channel2.write(buffer);
System.out.println("channel2-position= " + channel2.position());
ByteBuffer buffer1 = ByteBuffer.allocate((int)channel2.size());
channel2.position(0);
int i = channel2.read(buffer1);
System.out.println(new String(buffer1.array())); //读
channel2.position(0);
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
byte[] content = new byte[(int)channel2.size()];
int j = -1;
int index = 0;
while( (j = channel2.read(byteBuffer)) != -1){
System.arraycopy(byteBuffer.array(), 0, content, index, j );
byteBuffer.clear();
index = index + j;
}
System.out.println(new String(content));
//感觉nio不太适合做一个简单的文件的读然后展示 //先读再写
channel2.position(0);
ByteBuffer tempBuffer = ByteBuffer.allocate(124);
int size = -1;
long channel2Position = channel2.position();
while((size = channel1.read(tempBuffer)) != -1){
//这个方法一定得调用,不然就读取的是位置从当前位置开始的了
tempBuffer.flip();
channel2.write(tempBuffer, channel2Position);
channel2Position = channel2Position + size;
tempBuffer.clear();
} //使用直接缓冲区,映射到内存
channel1.position(0);
System.out.println(channel1.size());
MappedByteBuffer mappedByteBuffer = channel1.map(FileChannel.MapMode.READ_ONLY, 0 ,channel1.size());
// 这样一步不能有,直接映射的不会改变position mappedByteBuffer.flip();
channel3.write(mappedByteBuffer); //传输到其他通道 transferTo 怎么感觉直接传输很爽,不用那种读写了
channel1.transferTo(0, channel1.size(), channel4); //其他通道传进来transferFrom 注意这个position是针对自己的position
channel4.transferFrom(channel1, channel1.size(), channel1.size()); } }
现在来看看Selector
概述:
SelectableChannel对象的多路复用器。
可通过调用此类的open方法创建选择器,该方法将使用系统默认的选择器提供者创建新的选择其。也可通过调用自定义选择器提供者的openSeector方法来创建选择其。在通过选择器的close方法关闭之前,它一直保持打开状态。
通过SelectionKey对象来表示可选择通道到选择器的注册。选择器维护了三中选择健集合:
健集:包含的健表示当前通道到此选择器的注册,此集合由keys方法返回
已选择健集:就是这样一种健集合,即在前一次选择操作期间,检测每个健的通道是否已经至少为该健的相关操作集所标识的一个操作准备就绪。此集合由selectedKeys方法返回
已取消健集:是已被取消但其通道尚未朱小的健的集合
新创建的选择器中,这三个集合都是空集合。
通过某个通道的register方法注册该通道时,带来的副作用是向选择器的健集中添加了一个健。在选择操作期间从健集中移除已取消的健。健集本身是不接直接修改的
不管是通过关闭某个健的通道还是调用该健的cancel方法来取消健,该健都被添加到其选择器的已取消健集中。取消某个健会导致在下一次选择操作期间注销该健的通道,而且在注销时将所有选择器的健集中移除该健
通过选择操作将健添加到已选择健集中。可通过调用已选择健集的remove方法,或者通过调用从该健集获得的iterator的remove方法直接移除某个健。通过任何其他方式都不会将健从已选择健集中移除
选择
在每次选择期间,都可以将健添加到选择器的已选择健集以及从中将其移除,并且可以从其健集和已取消健集中将其移除。选择是由select()、select(long)、selectNow()方法执行的,执行具体步骤:
1、将已取消健集中的每个健从所有健集中移除,兵注销其通道
2、开始进行选择操作时,查询基础操作系统来更新每个剩余通道的准备就绪信息,以执行由其健的相关集合所标识的任意操作。对于已为至少一个这样的操作准备就绪的通道,执行以下两种操作之一:
a.如果该通道的健尚未在已选择的健集中,则将其添加到该集合中,并修改其准备就绪操作集,以准确地标识那些通道现在已报告为之准备就绪的操作。丢弃准备就绪操作集中以前的所有准备就绪信息
b.如果该通道的健已经在已选择健集中,则修改其准备就绪操作集,以准确地标识所有通道已报告位置准备的就绪的新操作。保留准备就绪操作集合以前的记录的所有准备就绪信息;换句话说,基础系统所返回的准备就绪操作集是和该健当前准备就绪操作集按位分开的
3、如果在步骤2的执行过程中要将任意健添加到已取消健集中,则处理过程如步骤1
是否阻塞选择操作以等待一个或多个通道准备就绪,如果这样做的话,要等待多久,这是三种选择方法之间唯一的本质区别
并发性:
选择器自身可以由多个并发线程安全使用,但是其健集并非如此
选择操作在选择器本身上、在健集上和在已选择健集上是同步的,顺序也与此顺序相同。在执行上面的1和3时,它们在已选择取消健集上也是同步的
在执行选择操作的过程中,更改选择器的相关集合对该操作没有影响;进行下一次选择操作才会看到此更改
可在任意时间取消和关闭通道。因此在一个或多个选择器的健集中出现某个健并不意味这该健是有效的,也不意味着该通道处于打开状态。如果存在另一个线程取消某个健或关闭某个通道的可能性,那么应用程序代码进行同步时应该小心,而且必要时应该检查这些条件。
阻塞在select()或者select(long)方法之一的某个线程可能被其他线程以下列三种方式之一中断:
调用选择器的wakeup方法
通过调用选择器的close方法
或者在通过调用已阻塞线程的interrupt方法的情况下,将设置其中断状态并且将调用该选择其的wakeup方法
方法:
abstract void close()
abstract boolean isOpen()
abstract static<SelectionKey> keys() 返回此选择器的健集
static Selector open() 打开一个选择器
abstract SelectorProcider provider() 返回创建此通道的提供者
abstract int select() 选择一组健,其相应的通道为I/O操作准备就绪
abstract int select(long timeout)
abstract Set<SelectionKey> selectedKeys() 返回此选择器的已选择健集
abstract int selectNow() 选择一组健,其相应的通道已为I/O操作准备就绪
static Selector wakeup() 使得尚未返回的第一个选择操作立即返回
前面的可以用于选择器的SelectableChannel:
概述:
可通过Selector实现多路复用的通道
为了与选择器一起使用,此类的实例必须首先通过register方法进行注册。此方法返回一个表示该通道已向选择器注册的新SelectionKey对象
向选择器注册后,通道在注销前将保持注册状态。注销涉及释放选择器已分配给该通道的所有资源
不能直接注销通道,相反,必须取消表示通道注册的健。取消某个健要求在选择器的下一个选择操作期间注册通道。可通过调用某个健的cancel方法显式地取消该健。无论是通过调用通道的close方法,还是中断阻塞于该通道上I/O操作中的线程来关闭该通道,都会隐式地取消该通道的所有健。
一个通道至多只能在任意特定选择器上注册一次
可以调用isRegistered方法来去顶顶是否向一个或多个选择器注册了某个通道
阻塞模式:
可选择的通道要么处于阻塞模式,要么处于非阻塞模式。在阻塞模式中,每一个I/O操作完成之前都会阻塞在其通道调用的其他I/O操作。在非阻塞模式中,永远不会阻塞I/O操作,并且传输的字节可能少于请求的数量,或者可能根本不传输字节。可用过调用可选择通道的isBlocking方法来确定其阻塞模式。
新创建的可选通道总是处于阻塞模式
方法:
abstract Object blockingLock() 获取其configureBlocking和register方法实现同步的对象
abstract SelectableChannel configureBlocking(boolean block) 调整此通道的阻塞模式
abstract boolean isBlocking() 判断此通道上的每个I/O操作在完成前是否被阻塞
abstract boolean isRegistered() 判断此通道当前是否已向任何注册器注册
abstract SelectionKey keyFor(Slector sel) 获取表示通道向给定选择器注册的健
abstract SelctorProvider provider() 返回创建此通道的提供者
SelectionKey register(Selector sel, int pos) 向给定的选择器注册此通道,返回一个选择健
abstract SelectionKey register(Selector sel, int ops, Object att) 向给定的选择器注册此通道,返回一个选择健
abstract int validOps() 返回一个操作集,标识此通道所支持的操作
前面注册的标记SelectionKey
概述:
表示SelectableChannel在Selector中的注册标记
字段:
static int OP_ACCEPT 用于套接字接受操作的操作集位
static int OP_CONNECT 用于套接字链接操作的操作集位
static int OP_READ 用于读取操作的操作集位
static int OP_WRITE 用于写入操作的操作集位
方法:
Object attach(Object ob) 将给定的对象附加到此健
Object attachment() 获取当前的附加对象
abstract void cancel() 请求取消此健的通道到其注册其的注册
abstract SelectableChannel channel() 返回为之创建此健的通道
abstract int intersetOps() 获取此健的interest集合
abstract SelectionKey intersetOps(int ops)将此健的interest集合设置为给定值
boolean isAcceptable() 测试此健的通道是否准备号接受新的套接字连接
boolean isConnectable() 测试此健的通道是否已完成其套接字进行连接操作
boolean isReadble() 测试此健的通道是否已准备好进行读取
abstract boolean isValid() 告知此健是否有效
boolean isWritable() 测试此健的通道是否已准备好进行写入
abstract int readOps() 获取此健的ready操作集合
abstract Selector selector() 返回此选择器创建的键
准备测试Selector。发现channel必须是Selectable的子类。发现FileChannel不是它的子类,。。。。
直接子类是AbstractSelectableChannel
特有方法:
Object blockingLock() 获取其configureBlocking和register方法实现同步的对象
protected void implCloseChannel() 关闭此通道
protected abstract void implConfigureBlockung(boolean block) 调整此通道的阻塞模式
然后只能找该类的子类玩。这不我们不是还得Socket操作吗,于是ServerSocketChannel和SocketChannel
概述:
针对面向流的侦听套接字的可选择通道
服务器套接字通道不是侦听网络套接字的完整抽象。必须通过调用socket方法所获得的关联ServerSocket对象来完成套接字选项的绑定和操作
。不可能为任意的已有服务器套接字创建通道,也不可能指定与服务器套接字通道关联的服务套接字所使用的SocketImpl对象
通过调用此类的open方法创建服务器套接字通道。新创建的服务器套接字通道已打开,但尚未绑定。视图调用未绑定的服务套接字通道的accept方法会导致抛出NotYetBoundException。可用过调用相关服务器套接字的某个bind方法来绑定服务器套接字通道。
多个并发线程可安全地使用服务器套接字通道
方法:
abstract SocketChannel accept() 接受到此通道套接字的连接
static ServerSocketChannel open()打开服务器套接字通道
int validOps() 返回一个操作集,标识此通道所支持的操作
概述:
此类实现服务器套接字。服务器套字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果
构造方法:
ServerSocket()
ServerSocket(int port)
ServerSocket(int port, int backlog) backlog-侦听 backlog 长度
ServerSocket(int port, int backlog, InetAddress bindAddr)
方法:
Socket accept() 侦听并接受到此套接字的连接
void bind(SocketAddress endpoint) 将ServerSocket绑定到特定地址(IP 地址和端口号)
void bind(SocketAddress endpoint, int backlog)
void close()
ServerSocketChannel getChannel() 得到与此套接字关联的唯一ServerSocketChannel对象(如果有)
InetAddress getInetAddress()
int getLocalPort()
SocketAddress getLocalSocketAddress()
int getReceiveBufferSize() 获取此ServerSocket的SO_RECVBUF选项的值,该值是将用于从此ServerSocket接受的套接字的建议缓冲区大小
boolean getResourceAddress() 测试是否启用SO_REUSEADDR
int getSoTimeout() 获取SO_TIMEOUT的值
protect void implAccept(Socket s) ServerSocket的子类使用此方法重写accept()以返回它们自己的套接字子类
boolean isBound() 返回ServerSocket的绑定状态
boolean isClose()
void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 设置此ServerSocket的性能首选项
void setReceiveBufferSize(int size)为从此ServerSocket接受的套接字的SO_RCVBUF选项设置默认建议值
static void setSocketFactory(SocketImplFactory fac) 为应用程序设置服务器套接字实现工厂
void setTimeout(int timeout) 设置超时时间,毫秒为单位
注意:这代码有问题执行会报错,我不想搞了先。觉得有帮助的留个解决方案呗。 package kunpu.nio; import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set; /**
* @author zhen
*/
public class SelectorOperator { public static void main(String[] args) {
new Thread(()->{
try{
server();
}catch (Exception e){
e.printStackTrace();
}
}).start();
new Thread(()->{
try{
client();
}catch (Exception e){
e.printStackTrace();
}
}).start();
} public static void client() throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
socketChannel.write(ByteBuffer.wrap("宝贝".getBytes()));
} public static void server() throws Exception {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8888));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_CONNECT); while(true){
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey selectionKey = null;
while(iterator.hasNext()){
selectionKey = iterator.next();
iterator.remove();
if(selectionKey.isReadable()){
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(byteBuffer);
if(readBytes != -1){
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, readBytes));
ByteBuffer byteBuffer1 = ByteBuffer.wrap("收到了".getBytes());
socketChannel.write(byteBuffer1);
}
}
}
}
} }