【转】java NiO 学习笔记
【转】java NiO 学习笔记
小灰灰Blog 发表于7个月前
【转】java NiO 学习笔记
  • 发表于 7个月前
  • 阅读 26
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

1. Channel

NIO中常用的通道有四个:

  • FileChannel : 文件
  • DatagramChannel : UDP 读取网络数据
  • SocketChannel : TCP 读取网络数据
  • ServerSocketChannel : 监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel

Java NIO的通道类似流,但又有些不同:

  • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。

  • 通道可以异步地读写。

  • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

    http://ifeve.com/wp-content/uploads/2013/06/overview-channels-buffers.png

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 bytes
ByteBuffer 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

http://ifeve.com/wp-content/uploads/2013/06/buffers-modes.png

capacity

表示容量

position

表示当前读写的位置,读模式时,从该位置开始读数据,读完之后,移到下一个位置;写模式时,从该位置写入数据,写完之后,移到下一个可写的位置;当有写切换到读时,重置为0

limit

限制,读模式下,表示最多可以读多少数据;在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity

分配buffer

> 每一个Buffer类都有一个allocate方法

ByteBuffer buf = ByteBuffer.allocate(48);

CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中写数据

  1. 从Channel写到Buffer。

    int bytesRead = inChannel.read(buf);

  2. 通过Buffer的put()方法写到Buffer里。

    buf.put(127);

模式切换 flip()方法

> flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值

从Buffer中读取数据

  1. 从Buffer读取数据到Channel

    int bytesWritten = inChannel.write(buf);

  2. 使用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

http://ifeve.com/wp-content/uploads/2013/06/scatter.png

使用方法,channel,读取数据到一个 Buffer数组中,填充满一个之后再填充下一个

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);

Gathering Writes

将多个Buffer中的数据写入Channel

http://ifeve.com/wp-content/uploads/2013/06/gather.png

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

//write data into buffers

ByteBuffer[] 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会再次将其放入已选择键集中。

```java
Selector 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();
标签: Java NIO
共有 人打赏支持
粉丝 136
博文 113
码字总数 190823
×
小灰灰Blog
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: