ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

NIO的知识点总结

2020-05-15 16:03:35  阅读:236  来源: 互联网

标签:总结 知识点 NIO Buffer Selector SelectionKey 方法 select 通道


参考文档链接:  https://www.zhihu.com/question/29005375

         http://ifeve.com/java-nio-all/

         https://www.yiibai.com/java_nio/java-nio-channels.html 

1.nio定义

  nio是非阻塞IO

2.IO、NIO的区别

  IO是面向流的阻塞IO,NIO是面向缓存的非阻塞IO。面向流的IO可以理解为以字节的处理数据,面向缓存是以块的形式处理数据。

3.三个核心

(1)Buffer - 缓冲

(2)Channel - 通道

(3)Selector - 选择器

4.Channel与Buffer的区别

  Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物)。而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理!Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区。

5.Buffer

基本用法

  写入数据到Buffer

  调用flip()方法

  从Buffer中读取数据

  调用clear()方法或者compact()方法

三个属性

(1)capacity:作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”。你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

(2)position:当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1。

(3)limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

常用方法

(1)flip()

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

(2)clear() 与 compact()

  如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

  如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

  如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

(3)mark() 与 reset()

  通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position

(4)equals() 与 compareTo()

equals() 只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。当满足下列条件时,表示两个Buffer相等:

(1)有相同的类型(byte、char、int等)。

(2)Buffer中剩余的byte、char等的个数相等。

(3)Buffer中所有剩余的byte、char等都相同。

compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:

(1)第一个不相等的元素小于另一个Buffer中对应的元素 。

(2)所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。

6.Channel

常用的通道

  FileChannel

  DatagramChannel

  SocketChannel

  ServerSocketChannel

通道实现

在Java NIO中,主要使用的通道如下:

  (1)FileChannel:文件通道用于从文件读取数据。它只能通过调用getChannel()方法来创建对象。不能直接创建FileChannel对象。注意,FileChannel不能设置为非阻塞模式。下面是一个创建FileChannel对象的例子:

FileInputStream fis = new FileInputStream("D:\\file-read.txt"); // Path of Input text file  

ReadableByteChannel rbc = fis.getChannel();

  (2)DatagramChannel:数据报通道可以通过UDP(用户数据报协议)通过网络读取和写入数据。它使用工厂方法来创建新对象。下面是打开DatagramChannel的语法:

DatagramChannel ch = DatagramChannel.open();

用于关闭DatagramChannel的语法:

DatagramChannel ch = DatagramChannel.close();

  (3)SocketChannel:数据报通道可以通过TCP(传输控制协议)通过网络读取和写入数据。 它还使用工厂方法来创建新对象。

用于打开SocketChannel的语法:

SocketChannel ch = SocketChannel.open();  

ch.connect(new InetSocketAddress("somehost", someport));

用于关闭SocketChannel的语法:

SocketChannel ch = SocketChannel.close();  

ch.connect(new InetSocketAddress("somehost", someport));

  (4)ServerSocketChannelServerSocketChannel允许用户监听传入的TCP连接,与Web服务器相同。对于每个传入连接,都会为连接创建一个SocketChannel

下面是打开ServerSocketChannel的语法:

ServerSocketChannel ch = ServerSocketChannel.open();  

ch.socket().bind (new InetSocketAddress (somelocalport));

下面是关闭ServerSocketChannel的语法:

ServerSocketChannel ch = ServerSocketChannel.close();  

ch.socket().bind (new InetSocketAddress (somelocalport));

7.Selector

定义

  选择器(Selector)用于使用单个线程处理多个通道。 因此,它需要较少的线程来处理这些通道。 线程之间的切换对于操作系统来说是昂贵的。 因此,使用它可以提高系统效率。

Selector的创建

Selector selector = Selector.open();

向Selector注册通道

为了将Channel和Selector配合使用,必须将channel注册到selector上。与Selector一起使用时,Channel必须处于非阻塞模式下。通过SelectableChannel.register()方法来实现,如下:

channel.configureBlocking(false);
SelectionKey key =channel.register(selector,Selectionkey.OP_READ);

SelectionKey

当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性:

  • interest集合
  • ready集合
  • Channel
  • Selector

interest集合

通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。这四种事件用SelectionKey的四个常量来表示:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

可以通过SelectionKey读写interest集合,像这样:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;

boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;

可以看到,用“位与”操作interest 集合和给定的SelectionKey常量,可以确定某个确定的事件是否在interest 集合中。

ready集合

ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合:

int readySet = selectionKey.readyOps();

可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型:

selectionKey.isAcceptable();

selectionKey.isConnectable();

selectionKey.isReadable();

selectionKey.isWritable();

通过Selector选择通道

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

下面是select()方法:

  • int select()
  • int select(long timeout)
  • int selectNow()

select()阻塞到至少有一个通道在你注册的事件上就绪了。

select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。

selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。

select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。

selectedKeys()

可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示:

Set selectedKeys = selector.selectedKeys();

当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。

可以遍历这个已选择的键集合来访问就绪的通道。如下:

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(); }

这个循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件。

注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。

wakeUp()与close()

某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。

如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。

用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

完整的示例

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();

  }

}

8.Scatter/Gathe

  分散读取(scatter):将一个通道中的数据分散读取到多个缓冲区中

  聚集写入(gather):将多个缓冲区中的数据集中写入到一个通道中

9. pipe

创建管道

 通过Pipe.open()方法打开管道。例如:

Pipe pipe = Pipe.open();

向管道写数据

要向管道写数据,需要访问sink通道。像这样:

Pipe.SinkChannel sinkChannel = pipe.sink();

通过调用SinkChannel的write()方法,将数据写入SinkChannel,像这样:

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {

    sinkChannel.write(buf);

}

从管道读取数据

从读取管道的数据,需要访问source通道,像这样:

Pipe.SourceChannel sourceChannel = pipe.source();

调用source通道的read()方法来读取数据,像这样:

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = sourceChannel.read(buf);

read()方法返回的int值会告诉我们多少字节被读进了缓冲区。

 

标签:总结,知识点,NIO,Buffer,Selector,SelectionKey,方法,select,通道
来源: https://www.cnblogs.com/1211dani/p/12895426.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有