JAVA NIO 笔记

来源:互联网 发布:java log4j的使用 编辑:程序博客网 时间:2024/05/27 16:43

NIO相比BIO的优势:

很多的 Java 应用程序受到等待I/O传输数据的束缚,传统的BIO通过采用每一个线程管理一个链接的方式,导致大量的线程开销,大量时间和内存资源消耗在大量的线程上面。NIO的就绪选择和多元执行使得一个或很少几个线程就能够有效率地同时管理多个 I/O 通道(channels)。真正的就绪选择由操作系统来做,可以更加充分的发挥机器的性能。

NIO中的一些概念:

1、可选择通道 SelectableChannel

Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。从SelectableChannel引申而来的类可以和支持有条件的选择的选择器(Selectors)一起使用。

2、选择器 Selector

选择器提供了询问通道是否已经准备好执行每个I/0操作的能力。

就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/O 通道(channels),真正的就绪选择必须由操作系统来做。操作系统的一项最重要的功能就是处理I/O请求并通知各个线程它们的数据已经准备好了,选择器类提供了这种抽象,使得Java代码能够以可移植的方式,请求底层的操作系统提供就绪选择服务

3、选择键 SelectionKey

register( ) 注册, 一个选择键,维护了一个可选择通道对象、一个选择器对象以及他们之间感兴趣和准备好的事件。如果对可选择通道和选择器进行重复注册register,只会更新interestOps,将会返回相同的选择键。

interestOps( )方法获取感兴趣的事件,即注册的事件。

readyOps( )方法获取从上次调用select()方法后已经准备好的事件,这些事件当然也是interestOps( )中的事件的子集。

4、缓冲区 Buffer

缓冲区主要包含下面几个属性:
容量(Capacity) 缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
上界(Limit) 缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。
位置(Position) 下一个要被读或写的元素的索引。位置会自动由相应的get( )和put( )函数更新。
标记(Mark) 一个备忘位置。调用mark( )来设定mark = postion。调用reset( )设定position = mark。标记在设定前是未定义的(undefined)。
关键方法:

flip( )  函数将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态。

缓存区分为直接缓冲区和非直接缓冲区。 直接ByteBuffer是通过调用具有所需容量的ByteBuffer.allocateDirect()函数产生的。wrap()函数所创建的被包装的缓冲区总是非直接的。直接缓冲区使用的内存是通过调用本地操作系统方面的代码分配的,绕过了标准JVM堆栈。直接缓冲区时I/O的最佳选择,但可能比创建非直接缓冲区要花费更高的成本。通常非直接缓冲不可能成为一个本地I/O操作的目标,如果您向一个通道中传递一个非直接ByteBuffer对象用于写入,最后也会导致创建一个临时的直接ByteBuffer对象。

NIO的Select过程:

Select过程,即为调用Selector.select( ) 方法的过程。

Selector中包含三种类型的键集合,已注册的键集合keys( )、已选择的键集合selectedKeys( )和已取消的键集合(内部私有成员)。

调用select( )方法时:

1、检查已取消的键集合,如果非空,从已经注册和已经取消的集合中,将这些取消的键移除,最后清空已取消的键集合;

2、检查已注册的键集合,判断哪些是感兴趣的键事件,然后通过调用底层操作系统的select( )本地方法获取就绪状态(可能会堵塞,直到系统调用完成);

对于那些操作系统指示至少已经准备好interest集合中的一种操作的通道,将执行以下两种操作中的一种:

a.如果通道的键还没有处于已选择的键的集合中,那么键的ready集合将被清空,然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。

b.如果键在已选择的键的集合中。键的ready集合将被表示操作系统发现的当前已经准备好的操作的比特掩码更新。之前的已经不再是就绪状态的操作不会被清除。一旦键被放置于选择器的已选择的键的集合中,它的ready集合将是累积的。比特位只会被设置,不会被清理(因此在处理已选择键集合selectedKeys( )时,需要移除已经处理的键)。

3.步骤2可能会花费很长时间,特别是所激发的线程处于休眠状态时。与该选择器相关的键可能会同时被取消。当步骤2结束时,步骤1将重新执行,以完成任意一个在选择进行的过程中,键已经被取消的通道的注销。

4.select操作返回的值是ready集合在步骤2中被修改的键的数量,而不是已选择的键的集合中的通道的总数。返回值是从上一个select( )调用之后进入就绪状态的通道的数量。之前的调用中就绪的,并且在本次调用中仍然就绪的通道不会被计入,而那些在前一次调用中已经就绪但已经不再处于就绪状态的通道也不会被计入。这些通道可能仍然在已选择的键的集合中,但不会被计入返回值中。

NIO的使用示例:

  // 1、准备服务端通道
  ServerSocketChannel ssc = ServerSocketChannel.open();
  ssc.configureBlocking(false);
  ServerSocket serverSocket = ssc.socket();
  serverSocket.bind(new InetSocketAddress(8888));

  // 2、注册接收连接事件
  Selector selector = Selector.open();
  ssc.register(selector, SelectionKey.OP_ACCEPT);

  // 3、监听网络事件

  while (true) {
     selector.select();
    // 遍历选择键
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while (iterator.hasNext()) {
       SelectionKey selectionKey = iterator.next();
       iterator.remove(); // 删除选择键

       // 处理网络事件

       if (selectionKey.isAcceptable())  { // 可以接收连接

                // 接收事件,对接收到的连接注册可读事件

       } else if (selectionKey.isReadable()) {  //可以读取数据

               //  读取数据,如果此时就要写数据,可以注册写事件

       } else if (selectionKey.isWritable()) { // 可以写数据

              //  写数据

       }
  }

总结:

JAVA NIO的核心是选择器Selector,是Java充分利用操作系统的特性,高效处理IO的方案, 它是用JAVA构建高性能服务器不可缺少的元素。

 

《NIO编程注意事项》:http://blog.csdn.net/liu251/article/details/7004346

《NIO框架中的多个Selector结构》:http://www.iteye.com/topic/482269