欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于NIO的一些总结

程序员文章站 2022-07-14 18:17:40
...

上一篇博客讲了io流的一些基本操作,这次讲一下NIO,NIO这个实际上在企业开发中很少用到的,大家适当看下就好

简介
传统的 java.io包,提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
java.io包的好处是代码比较简单、直观,缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。

同步:一条线程执行,其他线程就只能等待
异步:一条线程在执行,其他线程无需等待,照样可以继续执行

NIO和IO的区别:

  1. IO 面向流,NIO是面向缓冲区
  2. IO是阻塞的,NIO是非阻塞的
  3. NIO使用选择器
    NIO的优点:非阻塞,可以实现多路复用, nIO异步非阻塞

NIO的三个组成部分
Channel(通道)、Buffer(缓冲区)、Selector(选择器)

Buffer
Buffer主要有如下几种:

  • ByteBuffer

  • CharBuffer

  • DoubleBuffer

  • FloatBuffer

  • IntBuffer

  • LongBuffer

  • ShortBuffer
    关于NIO的一些总结
    Buffer的使用:以ByteBuffer举例

  • ByteBuffer对象的创建方式:

    • static ByteBuffer allocate(int capacity) 分配一个新的字节缓冲区。
    • static ByteBuffer allocateDirect(int capacity) 分配新的直接字节缓冲区。
    • static ByteBuffer wrap(byte[] array) 将 byte 数组包装到缓冲区中。
     //1.创建间接缓存区--缓存区是在:堆中:创建的快;访问的慢;
        ByteBuffer buf1 = ByteBuffer.allocate(10);

        //2.创建直接缓存区--系统内存:创建的慢;访问的快;
        ByteBuffer buf2 = ByteBuffer.allocateDirect(10);

        //3.根据一个已有的byte[]数组创建一个缓存区对象:间接缓存区
        byte[] byteArray = {97, 98, 99, 100};
        ByteBuffer buf3 = ByteBuffer.wrap(byteArray);
  • 缓冲区存取数据的两个核心方法:

    • put();存入数据到缓冲区中
    • get();获取缓冲区的数据
  • 缓冲区中的4个核心属性:

    • capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。
    • limit:代表有多少数据可以取出或有多少空间可以写入
    • position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置
    • mark:表示记录当前position位置,可以通过reset()恢复到mark的位置
    • 关系:mark <= position <= limit <= capacity
  • Buffer常用方法:

    • allocate() 获取Buffer对象
    • put() 存储数据
    • flip() 切换读取数据的模式
    • get() 读取数据
    • rewind() 可以重复读
    • clear() 清空整个缓冲区
    • mark() 在此缓冲区的位置设置标记。
    • reset() 将此缓冲区的位置重置为以前标记的位置
 public static void main(String[] args) {
        /*
            Buffer的几个重要技术点
                Buffer常用方法:
                    - allocate() 获取Buffer对象
                    - put() 存储数据
                    - flip() 切换读取数据的模式
                    - get() 读取数据
                    - rewind() 可以重复读
                    - clear() 清空缓冲区
               属性:
                - capacity:表示缓冲区中最大存储数据的容量
                - limit:表示缓冲区中可以操作数据的大小
                - position:表示缓冲区中正在操作数据的位置
                - mark:表示记录当前position位置,可以通过reset()恢复到mark的位置
                - 关系:mark <= position <= limit <= capacity

         */
        byte[] bys = {97, 98, 99, 100, 101};
        ByteBuffer buffer = ByteBuffer.allocate(10);

        System.out.println("============allocate()============");
        System.out.println(buffer.position());// 0
        System.out.println(buffer.limit());// 10
        System.out.println(buffer.capacity());// 10

        System.out.println("============put()============");
        buffer.put(bys);
        System.out.println(buffer.position());// 5
        System.out.println(buffer.limit());// 10
        System.out.println(buffer.capacity());// 10

        System.out.println("============flip()============");
        buffer.flip();
        System.out.println(buffer.position());// 0
        System.out.println(buffer.limit());// 5
        System.out.println(buffer.capacity());// 10

        System.out.println("============get()============");
        byte[] bytes = new byte[buffer.limit()];
        buffer.get(bytes);
        System.out.println(new String(bytes, 0, buffer.limit()));// abcde
        System.out.println(buffer.position());// 5
        System.out.println(buffer.limit());// 5
        System.out.println(buffer.capacity());// 10

        System.out.println("============rewind()============");
        buffer.rewind();
        System.out.println(buffer.position());// 0
        System.out.println(buffer.limit());// 5
        System.out.println(buffer.capacity());// 10


        buffer.clear();
        System.out.println("============clear()============");
        System.out.println(buffer.position());// 0
        System.out.println(buffer.limit());// 10
        System.out.println(buffer.capacity());// 10

    }

Channel(通道)
Channel(通道):Channel是一个对象,可以通过它读取和写入数据。可以把它看做是IO中的流,不同的是:

  • Channel是双向的,既可以读又可以写,而流是单向的
  • Channel可以进行异步的读写
  • 对Channel的读写必须通过buffer对象

正如上面提到的,所有数据都通过Buffer对象处理,所以,您永远不会将字节直接写入到Channel中,相反,您是将数据写入到Buffer中;同样,您也不会从Channel中读取字节,而是将数据从Channel读入Buffer,再从Buffer获取这个字节。简而言之:Channel负责传输,buffer负责存储

因为Channel是双向的,所以Channel可以比流更好地反映出底层操作系统的真实情况。特别是在Unix模型中,底层操作系统通常都是双向的。

在Java NIO中的Channel主要有如下几种类型:

  • FileChannel:从文件读取数据的
  • DatagramChannel:读写UDP网络协议数据
  • SocketChannel:读写TCP网络协议数据
  • ServerSocketChannel:可以监听TCP连接
FileChannel的使用

我们将通过CopyFile这个实力让大家体会NIO的操作过程。CopyFile执行三个基本的操作:创建一个Buffer,然后从源文件读取数据到缓冲区,然后再将缓冲区写入目标文件。

public static void copyFileUseNIO(String src,String dst) throws IOException{
//声明源文件和目标文件
        FileInputStream fi=new FileInputStream(new File(src));
        FileOutputStream fo=new FileOutputStream(new File(dst));
        //获得传输通道channel
        FileChannel inChannel=fi.getChannel();
        FileChannel outChannel=fo.getChannel();
        //获得容器buffer
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        while(inChannel.read(buffer) != -1){
            //判断是否读完文件
            //重设一下buffer的position=0
            buffer.flip();
            //开始写
            outChannel.write(buffer);
            //写完要重置buffer,重设position=0,limit=capacity
            buffer.clear();
        }
        inChannel.close();
        outChannel.close();
        fi.close();
        fo.close();
}   
SocketChannel的使用
  • 客户端
public class Client {
  public static void main(String[] args) {
    try (SocketChannel socket = SocketChannel.open()) {
      socket.connect(new InetSocketAddress("localhost", 8888));
      //1.先发一条
      ByteBuffer buf = ByteBuffer.allocate(100);
      buf.put("你好服务器,我是客户端".getBytes());
      buf.flip();//limit设置为position,position设置为0
      socket.write(buf);//输出从position到limit之间的数据
      //2.再收一条,不确定字数是多少,但最多是100字节。先准备100字节空间
      ByteBuffer inBuffer = ByteBuffer.allocate(100);
      socket.read(inBuffer);
      inBuffer.flip();//limit设置为position,position设置为0
      String msg = new String(inBuffer.array(),0,inBuffer.limit());
      System.out.println("【客户端】收到信息:" + msg);
      socket.close();
   } catch (IOException e) {
      e.printStackTrace();
   }
    System.out.println("客户端完毕!");
 }
}

  • 服务端
public class Server {
    public static void main(String[] args) {
        try (ServerSocketChannel serverChannel = ServerSocketChannel.open())
        {
            serverChannel.bind(new InetSocketAddress(8888));

            System.out.println("【服务器】等待客户端连接...");
            SocketChannel accept = serverChannel.accept();//阻塞的
            System.out.println("【服务器】有连接到达...");
            //1.先发一条
            ByteBuffer outBuffer = ByteBuffer.allocate(100);
            outBuffer.put("你好客户端,我是服务器".getBytes());
            outBuffer.flip();//limit设置为position,position设置为0
            accept.write(outBuffer);//输出从position到limit之间的数据
            //2.再收一条,不确定字数是多少,但最多是100字节。先准备100字节空间
            ByteBuffer inBuffer = ByteBuffer.allocate(100);
            accept.read(inBuffer);
            inBuffer.flip();//limit设置为position,position设置为0
            String msg = new String(inBuffer.array(),0,inBuffer.limit());
            System.out.println("【服务器】收到信息:" + msg);
            accept.close();
      } catch (IOException e) {
            e.printStackTrace();
      }
  }
}

Selector
Channel : 通道 ,负责传输 类似: 铁轨

Buffer: 缓冲区,负责读写数据 类似 : 火车

Selector: 选择器,负责调度通道 类似: 指挥中心

选择器Selector是NIO中的重要技术之一,它与SelectableChannel联合使用了非阻塞的多路复用.使用它可以节省CPU资源,提供程序的运行效率.

多路是指:服务器同时监听多个"端口"的情况,每个端口都要监听多个多个客户端连接

  • 服务器端的非多路复用效果
    关于NIO的一些总结
  • 服务器端多路复用效果
    使用了多路复用,只需要一个县城就可以处理多个通道,降低内存占用率,减少cpu切换时间,在高并发,高频段业务环境下有非常重要的优势

Selector是一个对象,它可以注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。

有了Selector,我们就可以利用一个线程来处理所有的channels。线程之间的切换对操作系统来说代价是很高的,并且每个线程也会占用一定的系统资源。所以,对系统来说使用的线程越少越好。

1.如何创建一个Selector

Selector 就是您注册对各种 I/O 事件兴趣的地方,而且当那些事件发生时,就是这个对象告诉您所发生的事件。

Selector selector = Selector.open();

2.注册Channel到Selector

为了能让Channel和Selector配合使用,我们需要把Channel注册到Selector上。通过调用 channel.register()方法来实现注册:

channel.configureBlocking(false);// 非阻塞
SelectionKey key =channel.register(selector,SelectionKey.OP_READ);// 注册

注意,注册的Channel 必须设置成异步模式 才可以,否则异步IO就无法工作,这就意味着我们不能把一个FileChannel注册到Selector,因为FileChannel没有异步模式,但是网络编程中的SocketChannel是可以的。

3.关于SelectionKey

请注意对register()的调用的返回值是一个SelectionKey。 SelectionKey 代表这个通道在此 Selector 上注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。

SelectionKey中包含如下属性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

(1) Interest set

就像我们在前面讲到的把Channel注册到Selector来监听感兴趣的事件,interest set就是你要选择的感兴趣的事件的集合。你可以通过SelectionKey对象来读写interest set:

int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE; 

通过上面例子可以看到,我们可以通过用AND 和SelectionKey 中的常量做运算,从SelectionKey中找到我们感兴趣的事件。

SelectionKey.OP_ACCEPT : 接收连接就绪事件,表示服务器监听到了客户连接,服务器可以接收这个连接了

SelectionKey.OP_CONNECT:连接就绪事件,表示客户端和服务器的连接已经建立成功

SelectionKey.OP_READ: 读就绪事件,表示通道中有了可读的数据,可以执行读操作了

SelectionKey.OP_WRITE: 写就绪事件,表示已经可以向通道中写数据了(通道目前可以用于写操作)

(2) Ready Set

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

int readySet = selectionKey.readyOps();

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

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

(3) Channel 和 Selector
我们可以通过SelectionKey获得Selector和注册的Channel:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector(); 

(4) Attach一个对象

可以将一个对象或者更多信息attach 到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

还可以在用register()方法向Selector注册Channel的时候附加对象。如:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

4.关于SelectedKeys()
生产系统中一般会额外进行就绪状态检查

一旦调用了select()方法,它就会返回一个数值,表示一个或多个通道已经就绪,然后你就可以通过调用selector.selectedKeys()方法返回的SelectionKey集合来获得就绪的Channel。请看演示方法:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

当你通过Selector注册一个Channel时,channel.register()方法会返回一个SelectionKey对象,这个对象就代表了你注册的Channel。这些对象可以通过selectedKeys()方法获得。你可以通过迭代这些selected key来获得就绪的Channel,下面是演示代码:

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> 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();
}

这个循环遍历selected key的集合中的每个key,并对每个key做测试来判断哪个Channel已经就绪。

请注意循环中最后的keyIterator.remove()方法。Selector对象并不会从自己的selected key集合中自动移除SelectionKey实例。我们需要在处理完一个Channel的时候自己去移除。当下一次Channel就绪的时候,Selector会再次把它添加到selected key集合中。

SelectionKey.channel()方法返回的Channel需要转换成你具体要处理的类型,比如是ServerSocketChannel或者SocketChannel等等。

IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高

多路复用案例 不发送数据:
  • Server
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Server {
    public static void main(String[] args) throws IOException,
            InterruptedException {
        //1.同时监听三个端口:7777,8888,9999
        ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
        serverChannel1.bind(new InetSocketAddress(7777));
        serverChannel1.configureBlocking(false);// 设置异步

        ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
        serverChannel2.bind(new InetSocketAddress(8888));
        serverChannel2.configureBlocking(false);

        ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
        serverChannel3.bind(new InetSocketAddress(9999));
        serverChannel3.configureBlocking(false);

        //2.获取一个选择器
        Selector selector = Selector.open();
        //3.注册三个通道 注册服务端和客户端通道的 接收连接就绪事件
        SelectionKey key1 = serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
        SelectionKey key2 = serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
        SelectionKey key3 = serverChannel3.register(selector, SelectionKey.OP_ACCEPT);
        //4.循环监听三个通道
        boolean isRun = true;
        while (isRun) {
            System.out.println();
            System.out.println("等待客户端连接...");
            // 这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
            int keyCount = selector.select();//阻塞的
            System.out.println("有一个客户端连接成功...");
            System.out.println("keyCount = " + keyCount);
            System.out.println("注册通道的数量 = " + selector.keys().size());
            System.out.println("已连接的数量 = " +
                    selector.selectedKeys().size());
            System.out.println();
            //遍历已连接的每个通道
            Set<SelectionKey> set2 = selector.selectedKeys();
            Iterator<SelectionKey> it = set2.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                System.out.println("获取通道...");
                 //获得服务端的channel
                ServerSocketChannel channel =
                        (ServerSocketChannel) key.channel();
                System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
                // 获得服务端和客户端连接的通道
                SocketChannel socketChannel = channel.accept();
                //接收数据(略)
                
                it.remove();
            }
            System.out.println("休息1秒,等待下一个连接...");
            Thread.sleep(1000);
        }
    }
}

  • Client2
nel.close();
     } catch (IOException e) {
        e.printStackTrace();
     }
   }).start();
    new Thread(()->{
      SocketChannel socketChannel = null;
      try {
        socketChannel = SocketChannel.open();
        socketChannel.connect(new
                        InetSocketAddress("localhost",9999));
        socketChannel.close();
     } catch (IOException e) {
        e.printStackTrace();
     }
   }).start();
 }
}
多路复用,发送数据
  • Server
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class Server {
    public static void main(String[] args) throws IOException,
            InterruptedException {
        //1.同时监听三个端口:7777,8888,9999
        ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
        serverChannel1.bind(new InetSocketAddress(7777));
        serverChannel1.configureBlocking(false);// 设置非阻塞

        ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
        serverChannel2.bind(new InetSocketAddress(8888));
        serverChannel2.configureBlocking(false);

        ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
        serverChannel3.bind(new InetSocketAddress(9999));
        serverChannel3.configureBlocking(false);

        //2.获取一个选择器
        Selector selector = Selector.open();

        //3.注册三个通道  注册服务端和客户端通道的 接收就绪事件
//        serverChannel1.register(selector, SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
//        serverChannel2.register(selector, SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
//        serverChannel3.register(selector, SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);

        serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
        serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
        serverChannel3.register(selector, SelectionKey.OP_ACCEPT);


        //4.循环监听三个通道
        boolean isRun = true;
        while (isRun) {
            System.out.println();
            System.out.println("等待客户端连接...");
            // 这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
            int keyCount = selector.select();//阻塞的


            System.out.println("有一个客户端连接成功...");
            System.out.println("keyCount = " + keyCount);
            System.out.println("注册通道的数量 = " + selector.keys().size());
            System.out.println("已连接的数量 = " +
                    selector.selectedKeys().size());
            System.out.println();

            //遍历已连接的每个通道
            Set<SelectionKey> set2 = selector.selectedKeys();
            Iterator<SelectionKey> it = set2.iterator();
            /*ServerSocketChannel channel = null;
            SocketChannel socketChannel = null;*/
            while (it.hasNext()) {
                SelectionKey key = it.next();
                /*try {
                    System.out.println("获取通道...");
                    channel =
                            (ServerSocketChannel) key.channel();
                    System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
                    //  获得服务端和客户端连接的通道
                    socketChannel = channel.accept();

                    ByteBuffer buf = ByteBuffer.allocate(1000);
                    socketChannel.read(buf);
                    buf.flip();
                    System.out.println("接收到的数据是:" + new String(buf.array(), 0, buf.limit()));

                } catch (IOException e) {
                    e.printStackTrace();
                }

                it.remove();*/

                //接收数据(略)
                it.remove();
                //服务端的channel
                ServerSocketChannel serverChannel = null;
                //获得和客户端连接的通道
                SocketChannel socketChannel = null;
                 //客户端请求连接事件
                if (key.isAcceptable()) {
                    serverChannel = (ServerSocketChannel) key.channel();
                     //获得服务端和客户端连接的通道
                    socketChannel = serverChannel.accept();
                     //将服务端和客户端连接的设置非阻塞
                    socketChannel.configureBlocking(false);
                    //注册服务端和客户端通道的读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    //获取通道对象,方便后面将通道内的数据读入缓冲区
                    socketChannel = (SocketChannel) key.channel();
                    socketChannel.read(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(),0,buf.limit()));
                    Thread.sleep(5000);
                }
                System.out.println("休息1秒,等待下一个连接...");
               Thread.sleep(1000);
            }
        }

    }
}
  • 客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client2 {
    public static void main(String[] args) {
        new Thread(() -> {
            SocketChannel socketChannel = null;
            try {
                socketChannel = SocketChannel.open();
                socketChannel.connect(new
                        InetSocketAddress("localhost", 8888));

                ByteBuffer buf = ByteBuffer.allocate(1024);
                buf.put("服务器你好,我是客户端8888...".getBytes());
                buf.flip();
                socketChannel.write(buf);

                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            SocketChannel socketChannel = null;
            try {
                socketChannel = SocketChannel.open();
                socketChannel.connect(new
                        InetSocketAddress("localhost", 9999));

                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //1.先发一条
                ByteBuffer buf = ByteBuffer.allocate(100);
                buf.put("你好服务器,我是客户端9999....".getBytes());
                buf.flip();//limit设置为position,position设置为0
                socketChannel.write(buf);//输出从position到limit之间的数据



                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

NIO2
在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。但是对AIO来说,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在Java.nio.channels包下增加了下面四个异步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel,这个类提供了一个open()静态工厂,一个bind()方法用于绑定服务端IP地址(还有端口号),另外还提供了accept()用于接收用户连接请求。在客户端使用的通道是AsynchronousSocketChannel,这个通道处理提供open静态工厂方法外,还提供了read和write方法。在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO中的事件处理类是CompletionHandler<V,A>,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调。

void completed(V result, A attachment);

void failed(Throwable exc, A attachment);

相关标签: java开发 NIO