《Java NIO系列教程》学习笔记
Java NIO 由以下几个核心部分组成:
- Channels
- Buffers
- Selectors
Channel
FileChannel
:从文件中读写数据DatagramChannel
:能通过UDP读写网络中的数据SocketChannel
:能通过TCP读写网络中的数据ServerSocketChannel
:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
Buffer
使用Buffer读写数据一般遵循以下四个步骤:
-
写入数据到Buffer
// 读取channel中的数据到buf中
int bytesRead = inChannel.read(buf);
// 直接向buf写入数据
buf.put(127); -
调用
flip()
方法 -
从Buffer中读取数据
// 将buf中的数据写入到channel
int bytesWritten = inChannel.write(buf);
// 直接从buf读取数据
byte aByte = buf.get(); -
调用
clear()
方法或者compact()
方法
Buffer的capacity,position和limit
capacity
容量
position
当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
limit
写模式下,limit等于Buffer的capacity;读模式下,limit表示最多能读到的位置。当切换Buffer到读模式时,limit会被设置成写模式下的position值。
Buffer.rewind()
将position设回0,所以你可以重读Buffer中的所有数据。
通过调用Buffer.mark()
方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()
方法恢复到这个position。
Scatter/Gather
分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
channel.read(bufferArray);
聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。
channel.write(bufferArray);
Selector
通过调用Selector.open()
方法创建一个Selector:
/ 创建Selector
Selector selector = Selector.open();
为了将Channel和Selector配合使用,必须将channel注册到selector上:
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
- Connect
- Accept
- Read
- Write
如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来。
可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
select()
方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()
方法后有多少通道变成就绪状态。
selectedKeys()
一旦调用了select()
方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()
方法,访问“已选择键集(selected key set)”中的就绪通道。可以遍历这个已选择的键集合来访问就绪的通道。
注意每次迭代末尾的keyIterator.remove()
调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
SelectionKey.channel()
方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel
或SocketChannel
等。
Pipe
Java NIO 管道是2个线程之间的单向数据连接。Pipe
有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
// 创建管道
Pipe pipe = Pipe.open();
// sink通道
Pipe.SinkChannel sink = pipe.sink();
// 准备缓冲区,并向缓冲区写入数据
ByteBuffer buf = ByteBuffer.allocate(1024);
// bytes -> buf
buf.put("hello world!".getBytes());
buf.flip();
// buf -> channel
while (buf.hasRemaining()) {
sink.write(buf);
}
// source通道
Pipe.SourceChannel source = pipe.source();
// channel -> buf
buf.clear();
// 读取数据
source.read(buf);
buf.flip();
// buf -> print
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}