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);  //  **这里**
posted @ 2018-05-26 11:53  常烦常乐  阅读(116)  评论(0编辑  收藏  举报