1. Channel
NIO中常用的通道有四个:
- FileChannel : 文件
- DatagramChannel : UDP 读取网络数据
- SocketChannel : TCP 读取网络数据
- ServerSocketChannel : 监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
Java NIO的通道类似流,但又有些不同:
-
既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
-
通道可以异步地读写。
-
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
2. Buffer
和Channel进行交互,从Channel中读取数据,or将数据写入Channel
基本用法
当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入
读写数据的一般步骤
- 写入数据到Buffer:
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead =channel.read(buf)
- 调用flip()方法:
buf.flip()
- 从Buffer中读取数据 :
buf.get()
- 清空缓存区: clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();//create buffer with capacity of 48 bytesByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf); //read into buffer.while (bytesRead != -1) { buf.flip(); //make buffer ready for read while(buf.hasRemaining()){ System.out.print((char) buf.get()); // read 1 byte at a time } buf.clear(); //make buffer ready for writing bytesRead = inChannel.read(buf);}aFile.close();
三个参数 capacity,position和limit
capacity
表示容量
position
表示当前读写的位置,读模式时,从该位置开始读数据,读完之后,移到下一个位置;写模式时,从该位置写入数据,写完之后,移到下一个可写的位置;当有写切换到读时,重置为0
limit
限制,读模式下,表示最多可以读多少数据;在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity
分配buffer
每一个Buffer类都有一个allocate方法
ByteBuffer buf = ByteBuffer.allocate(48);CharBuffer buf = CharBuffer.allocate(1024);
向Buffer中写数据
-
从Channel写到Buffer。
int bytesRead = inChannel.read(buf);
-
通过Buffer的put()方法写到Buffer里。
buf.put(127);
模式切换 flip()方法
flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值
从Buffer中读取数据
-
从Buffer读取数据到Channel
int bytesWritten = inChannel.write(buf);
-
使用get()方法从Buffer中读取数据。
byte aByte = buf.get()
rewind()方法
Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
buf.rewind()
3.Scatter/Gather
scatter/gather用于描述从Channel中读取或者写入到Channel的操作
分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。
Scattering Reads
从Channel中读取数据到Buffer
使用方法,channel,读取数据到一个 Buffer数组中,填充满一个之后再填充下一个
ByteBuffer header = ByteBuffer.allocate(128);ByteBuffer body = ByteBuffer.allocate(1024);ByteBuffer[] bufferArray = { header, body };channel.read(bufferArray);
Gathering Writes
将多个Buffer中的数据写入Channel
ByteBuffer header = ByteBuffer.allocate(128);ByteBuffer body = ByteBuffer.allocate(1024);//write data into buffersByteBuffer[] bufferArray = { header, body };channel.write(bufferArray);
依次将数组中的数据写入Channel,一个写完再写如下一个(注意写入的是有效数据)
4. Selector
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接
创建
Selector selector = Selector.open();
通道注册
channel.configureBlocking(false);SelectionKey key = channel.register(selector, Selectionkey.OP_READ | SelectionKey.OP_WRITE); ``` 与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:- Connect : SelectionKey.OP_CONNECT- Accept : SelectionKey.OP_ACCEPT- Read : SelectionKey.OP_READ- Write : SelectionKey.OP_WRITE### SelectionKey通道注册之后,返回一个 `SelectionKey` 对象,其中包含一下属性:- interest集合- interest集合是你所选择的感兴趣的事件集合- 判断是否为Accept事件 : `boolean isInterestedInAccept = (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;`- ready集合- 通道已经准备就绪的操作的集合 `int readySet = selectionKey.readyOps()`- 检测channel中什么事件或操作已经就绪: `boolean state = selectionKey.isAcceptable();`- Channel- 获取Channel : `Channel channel = selectionKey.channel();`- Selector- 获取Selector : `Selector selector = selectionKey.selector();`- 附加的对象(可选)- 可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道- 可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象 selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();### 通过Selector选择通道> 一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。```javaSelector selector = Selector.open();channel.configureBlocking(false);SelectionKey key = channel.register(selector, SelectionKey.OP_READ);while(true) {int readyChannels = selector.select();if(readyChannels == 0) continue;Set selectedKeys = selector.selectedKeys();Iterator keyIterator = selectedKeys.iterator();while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove();}}
5. FileChannel
文件通道,用于读写文件,阻塞模式
打开FileChannel
需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例
通过 FileInputStream 获取 FileChannel 示意
FileInputStream fileInputStream = new FileInputStream(new File("/tmp/tags.log"));FileChannel fileChannel = (fileInputStream).getChannel();ByteBuffer buffer = ByteBuffer.allocate(128);int size = fileChannel.read(buffer);buffer.flip();while (buffer.hasRemaining()) { System.out.print(buffer.getChar());}fileInputStream.close();
使用 RandomAccessFile 获取 FileChannel 示意
RandomAccessFile randomAccessFile = new RandomAccessFile("/tmp/tags.log", "rw");FileChannel fileChannel = randomAccessFile.getChannel();String newData = "New String to write to file..." + System.currentTimeMillis();ByteBuffer buf = ByteBuffer.allocate(100);buf.clear();buf.put(newData.getBytes());buf.flip();while(buf.hasRemaining()) { fileChannel.write(buf);}// 强制写入文件fileChannel.force(true);ByteBuffer buffer = ByteBuffer.allocate(100);int size = fileChannel.read(buffer);buffer.flip();while (buffer.hasRemaining()) { byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes, 0, buffer.limit()); System.out.print(new String(bytes));}fileChannel.close();randomAccessFile.close();