关于NIO的一些总结

上一篇博客讲了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
    在这里插入图片描述
    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资源,提供程序的运行效率.

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

  • 服务器端的非多路复用效果
    在这里插入图片描述
  • 服务器端多路复用效果
    使用了多路复用,只需要一个县城就可以处理多个通道,降低内存占用率,减少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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值