1、概述
传统的Java io是面向流Stream的,阻塞IO;而nio则是面向缓冲区buffer的,非阻塞的io。区别是流中数据不能移动,而buffer可以移动。
Java nio选择器Selectors允许单个线程监控多个通道的输入和输出。
Java缓冲区Buffer,本质上其实是一个内存块,可以往里面写入数据,可以从里面读取数据。其被包装成NIO buffer对象,便于操作。
Buffer 一般和Channel配合使用。
一般使用步骤如下:
1.数据写入缓冲区,缓冲区会记录写入数据的大小。
2.调用flip方法,从写入模式转至读取模式。
3.从缓冲区读取数据
4.调用clear或者compact方法清除数据
2 Channel
2.1 channel与stram区别
channel 既可以读也可以写,而stream只能读或者写二选一。
channel可以异步读写
channel只能从buffer中进行读写
2.2 常见channel
FileChannel 从文件中读取数据
DatagramChannel 从UDP中进行读写
SocketChannel 从TCP中进行读写
ServerSocketChannel 允许监听进来的TCP连接,每个进来的连接都会创建一个SocketChannel
2.3用法
xxx.getChannel()方法获取一个Channel,然后和buffer配合读写数据。
3 Buffer容量、位置、限制
capacity:容量,buffer的大小。创建时确定,无法更改。
position:位置,被读取或写入的下一个元素的位置。
写模式:写入数据的位置,默认为0,最大可以是capacity-1
读模式:从position开始读取,然后position会自动增长,指向下一个读取位置。
从写模式切换到读模式时,会将position重置为0.
可以通过position(int)方法重设position
limit:限制,缓冲区中可以使用的元素个数。
写模式:limit 等于capacity,表示你可以写多少数据到缓冲区,l
读模式:limit表示你可以从缓冲区读取多少数据。
从写模式切换到读模式的时候,limit的值为写模式的position的值。
可以通过limit(int)方法重设limit
Mark 标记:
被记录的位置,调用mark方法时设置(mark=position),默认为-1;
buffer实际存储的对象时数组,其是非线程安全的
4 NIO buffer类型
java nio缓冲区有以下几种:
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
5 缓冲区的使用
5.1 创建
创建完成后,默认就是写模式。
ByteBuffer.allocate(int size)
在JVM中分配size字节大小的容量。
ByteBuffer. allocateDirect(int size)
在JVM之外,系统内存中分配size字节大小的的容量。
XXXBuffer.wrap(byte[] array)
使用已经分配的数组作为buffer管理。
5.2写缓冲区
方式一:从通道Channel
里面写数据到缓冲区。
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel fChannel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
//从fChannel中读取数据写入到buffer中,byteReads是返回的写入数据的大小,-1表示写完所有数据
int byteReads = fChannel.read(buffer);
方式二:使用buffer的putXXX方法
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel fChannel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
buf.put(127);
put方法有很多重载方式,支持各种方式的写入数据,具体可以查看Api。单参数的put方法会自动调整position的值,带有index参数的put,如put(int index, byte b)
并导致任何属性的变化。
5.3 flip
flip方法用于切换缓冲区从写模式到读模式。
调用flip 方法会讲position设置为0,同时将limit设置为之前的position的值。
5.4 读缓冲区
方式一:将数据从buffer读取到channel
//从buffer读取数据到channel,返回值byteReads为读取的数据大小,-1表示读取完所有数据。
byteReads = fChannel.read(buffer);
方式二:使用getXXX方法读取数据
getXXX方法同put方法类似
buf.get()
可以使用rewind()
方法,将position设置为0,然后重新从buffer读取数据。get()
会导致position的变化,get(int index)
不会导致任何属性的变化。
5.5 数据清除
读取完数据后,若需要再次写入,可以调用clear
或compact
方法清除数据
clear
方法将position设置为0,将limit设置为capacity,实际并没有清空数据,只是重置了写入的位置。
compact
方法将未读取的数据移动到缓冲区开始位置,将position设置在最后的未读元素后面,limit仍然设置为capacity。compact使得buffer可以再次写入,append到之前未读数据后面。
5.6 设置标记
mark()
方法可以在buffer中设置一个标记,记录当前position,然后若想回到这个标记的位置调用reset()
即可。
5.7 buffer之间的比较
equals()
比较buffer中剩余部分元素,其判断相等的条件:
1、两个buffer有数据相同的类型
2、buffer剩余元素的数目相等
3、buffer剩余元素一一相等
compareTo()
:比较两个buffer的剩余元素,bufferA比bufferB小的情况如下:
1、bufferA的元素小于bufferB中对应位置的元素
2、所有元素相等,但bufferA比bufferB元素个数少。
5.8 order方法
获取以及设置读写的字节序。
6 Scatter/Gather
Java NIO支持scatter/gather(分散/聚集),用于描述从通道读取和写入通道。使用场景:消息数据各部分需要分开传输,比如消息头和消息体组成的消息。
分散读是在读操作的时候,读取的数据被写入多个buffer中。scatter将数据从通道写入多个buffer。
聚集写,在写入多个buffer到一个通道,此时gather将数据从多个buffer写入通道。
6.1 read
read 把通道里的数据按照buffers数组中的顺序填充到相应的buffer中。当第一个buffer填充满后,才会填充下一个buffer。
ByteBuffer head = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {head, body};
channel.read(buffers);
6.2 write
write方法按照数组中的顺序,一个个的讲buffer中的内容写入Channel中。而且写入的时候是仅仅将buffer的position和limit之间的元素写入。而不是全部写入。
ByteBuffer head = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {head, body};
channel.write(buffers);