NIO有以下几个核心部分组成:
- Channels
- Buffers
- Selectors
Channel和Buffers
所有IO和NIO都从一个Channel开始。Channel有点流的概念。数据都可以从Channel读到Buffer中,也可以从Buffer写到Channel。
Channel主要实现类:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
Channel中的Scatter/Gather
分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个Buffer中。因此Channel将从Channel中读取的数据“分散”到多个Buffer中。
聚集(gather)写入Channel是指在写操作时操作多个Buffer的数据写入同一个Channel,因此,Channel将多个Buffer中的数据“聚集”然后发送到Channel。
scatter/gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的Buffer中,这样你可以方便的处理消息头和消息体。
Buffer首先被插入到数组,然后再将数组作为Channel.read()的输入参数。read()方法按照Buffer在数组中的顺序将从channel中读取的数据写入到Buffer中,当一个buffer被写满后,channel紧接着向另一个buffer中写。
Scattering Reads在移动下一个Buffer钱,必须填满当前的Buffer,这意味着他不适用于动态消息。换句话说,如果存在不同的缓存区域,必须将前一个区域填充完毕,Scattering Reads才能正常工作。
Buffer数组是write()方法的入参,write()方法会按照Buffer在数组中的顺序,将数据写入到Channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但仅仅包涵58byte的数据,那么这58byte的数据会被写入到Channel中。因此与Scattering Reads相反,Gathering Writes能比较好的处理动态消息。
通道之间的数据传输
transferFrom()
FileChannel的transferFrom()方法可以将数据从源通刀传输到FileChannel中。
方法传入的参数position表示从position处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于count个字节,则所传输的字节数要下雨请求的字节数。此外要注意,在SocketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足count字节)。因此,SocketChannel的实现中,SocketChannel可能不会讲请求的所有数据全部传输到FileChannel中。
transferTo()
transferTo()方法将数据从FileChannel传输到其他的Channel中
在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个Channel传到另一个channel中。
Buffer读写数据一般遵循以下四个步骤:
1.写入数据到Buffer
2.调用flip()方法
3.从Buffer中读取数据
4.调用clear()方法或者compcat()方法
Buffer当中有三个属性:
- capacity 作为一个内存块,Buffer有一个固定的大小值也叫“capacity”。只能往里面写入capacity个byte、long、char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续往里写入数据。
- position 当写数据到Buffer中时,position表示当前位置,初始的position为0.当一个byte、long等数据写到Buffer中后,position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity-1。当读取数据时。也是从某个特定位置读。当Buffer从写模式切换到读模式,position会被重置为0,,当从Buffer的position处读取数据时,position向前移动到下一个可读位置。
- limit 在写模式下,Buffer的limit表示你最多能网Buffer里写多少个数据。写模式下,limit等于Buffer的capacity。
当切换到读模式时,limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据。此时limit为你之前写入的最大position。
Buffer的类型
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
Buffer的分配
要获取Buffer对象首先要进行分配。每一个Buffer类都有一个allocate方法。
向Buffer中写数据
写数据到Buffer中一共有两种方式:(1)从Channel写到Buffer中。(2)通过Buffer的put()方法写到Buffer里。
filp()方法
从Buffer中读取数据
(1)从Buffer读取数据到Channel。(2)使用get()方法从Buffer中读取数据。
rewind()方法
Buffer.rewind()方法将position设回0,所以你可以重读Buffer中的所有数据。limit不变。
clear()与compact()方法
一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或者compact()方法来完成。如果调用的是clear()方法,position将会被设回0,limit被设置成capacity的值。换句话说,Buffer被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里面写数据。如果Buffer中有一些未读数据,调用clear()方法,数据将“被遗忘。重新写入。如果Buffer中仍有未读的数据,且后续还需要这些数据,但此时箱套先写谢写数据,那么使用compact()方法。
mark()与reset()方法
mark可以标记到Buffer中某个特定的position。之后可以通过调用Buffer.reset()恢复到这个position。
Selector
Selector允许单线程处理多个Channel。如果你打开了多个连接通道,但每个连接流量都很低,使用Selector就会很方便。
要是用Selector,就得向Selector中注册Channel,然后调用他的select()方法。这个方法会一直阻塞到某个注册的通道有事件准备就绪。一旦这个方法返回,线程就可以处理这些事件。事件的例子有:新连接进来,数据接收等。