Java BIO与NIO以及AIO分析

Posted ywb-articles

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java BIO与NIO以及AIO分析相关的知识,希望对你有一定的参考价值。

一.BIO与NIO以及AIO的概念

  BIO是同步阻塞式的IO

  NIO是同步非阻塞的IO (NIO1.0,JDK1.4)

  AIO是非同步非阻塞的IO(NIO2.0,JDK1.7)

二.BIO简单分析

  1.简单分析

    BIO是阻塞的IO,原因在于accept和read会阻塞。所以单线程的BIO是无法处理并发的。

  2.案例

    服务端:

public class Bioserver 
    public static void main(String[] args) throws IOException //在开发中不要直接抛出去,有可能出现异常导致连接未关闭
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress("127.0.0.1",9999));

        byte[] b = new byte[1024];
        StringBuilder sb = new StringBuilder();
        System.out.println("服务端正在等待连接......");
        Socket socketAccept = serverSocket.accept();//这里会阻塞
        System.out.println("有客户端连接成功");
        while (true)
            System.out.println("等待消息......");
            int read = socketAccept.getInputStream().read(b);//这里会阻塞
            System.out.println("共读取了"+read+"个字节");

            String str = new String(b);
            sb.append(str);
            System.out.println("读取的数据内容为: " + sb);

        
    

    客户端:

public class BioClient 
    public static void main(String[] args) throws IOException 
        Socket socket = new Socket();
        Scanner scanner = new Scanner(System.in);
        socket.connect(new InetSocketAddress("127.0.0.1",9999));
        while(true)
            System.out.println("请输入内容");
            String next = scanner.next();

            socket.getOutputStream().write(next.getBytes());
        
    

    测试如下:

    技术图片

  3.多线程下的BIO

    多线程的BIO是可以处理并发的,但频繁的创建线程,运行线程以及销毁线程无疑是非常消耗资源的(即便可以使用线程池限制线程数量,但海量并发时,BIO的方式效率相比于NIO还是太低了)

    下面是一个多线程的BIO案例:

      服务端:

public class BioServerMultiThread 
    public static void main(String[] args) throws IOException 
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress("127.0.0.1",9999));
        System.out.println("服务端正在等待连接......");

        while (true)
            Socket socketAccept = serverSocket.accept();//这里会阻塞
            System.out.println("有客户端连接成功");

            new Thread(new Runnable() 
                @Override
                public void run() 
                    try
                        System.out.println("等待消息......");
                        String msg = "";
                        while (true)
                            byte[] b = new byte[1024];
                            int len = socketAccept.getInputStream().read(b);
                            if (len < 0)
                                continue;
                            
                            msg = new String(b,0,len);
                            System.out.println(msg);
                        
                    catch (Exception e)
                        e.printStackTrace();
                    
                
            ).start();
        
    

      客户端(创建多个客户端,代码相同):

public class BioClient1 
    public static void main(String[] args) throws IOException 
        Socket socket = new Socket();
        Scanner scanner = new Scanner(System.in);
        socket.connect(new InetSocketAddress("127.0.0.1",9999));
        String className = "这是来自"+Thread.currentThread().getStackTrace()[1].getClassName()+"的消息";
        while(true)
            System.out.println("请输入内容");
            String next = scanner.next();
            socket.getOutputStream().write(className.getBytes());
            socket.getOutputStream().write(next.getBytes());
        
    

      测试如下:

      技术图片

三.NIO简单分析

  NIO是非阻塞IO,其原因在于数据准备就绪后,由选择器通知给服务端,而在数据准备完毕之前,服务器无需等待。

  1.缓冲区

    在BIO操作中,所有的数据都是以流的形式操作的,而在NIO中,则都是使用缓冲区来操作。

    NIO中的缓冲区是Buffer类,它是java.nio包下的一个抽象类,详情可以查看JDK的API文档,例如下图

    技术图片

    打开Buffer类,可以看到如下图(其中带有的方法可以查看JDK文档)

     技术图片

  2.直接缓冲区和非直接缓冲区

    直接缓冲区是指将缓冲区建立在物理内存中,通过allocateDirect方法建立(效率高,但不安全)

    非直接缓冲区是指将缓冲区建立在JVM中,通过allocate方法建立(效率低,但安全)

  3.管道

    管道是NIO中传输数据的桥梁,它是java.nio包下的一个接口,若需查看其详细信息可以参考JDK的API文档,例如下图

    技术图片

  4.使用NIO实现文件拷贝两种方式案例

    直接缓冲区的方式:

    @Test
    public void test1() throws IOException 
        long statTime=System.currentTimeMillis();
        //创建管道
        FileChannel   inChannel=    FileChannel.open(Paths.get("E://test/aaa.txt"), StandardOpenOption.READ);
        FileChannel   outChannel=    FileChannel.open(Paths.get("E://test/bbb.txt"), StandardOpenOption.READ,StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        //定义映射文件
        MappedByteBuffer inMappedByte = inChannel.map(FileChannel.MapMode.READ_ONLY,0, inChannel.size());
        MappedByteBuffer outMappedByte = outChannel.map(FileChannel.MapMode.READ_WRITE,0, inChannel.size());
        //直接对缓冲区操作
        byte[] dsf=new byte[inMappedByte.limit()];
        inMappedByte.get(dsf);
        outMappedByte.put(dsf);
        inChannel.close();
        outChannel.close();
        long endTime=System.currentTimeMillis();
        System.out.println("操作直接缓冲区耗时时间:"+(endTime-statTime));
    

    非直接缓冲区的方式:

 @Test
    public void test2() throws IOException 
        long statTime=System.currentTimeMillis();
        // 读入流
        FileInputStream fst = new FileInputStream("E://test/bbb.txt");
        // 写入流
        FileOutputStream fos = new FileOutputStream("E://test/ccc.txt");
        // 创建通道
        FileChannel inChannel = fst.getChannel();
        FileChannel outChannel = fos.getChannel();
        // 分配指定大小缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        while (inChannel.read(buf) != -1) 
            // 开启读取模式
            buf.flip();
            // 将数据写入到通道中
            outChannel.write(buf);
            buf.clear();
        
        // 关闭通道 、关闭连接
        inChannel.close();
        outChannel.close();
        fos.close();
        fst.close();
        long endTime=System.currentTimeMillis();
        System.out.println("操作非直接缓冲区耗时时间:"+(endTime-statTime));
    

  5.选择器(Selector)

    它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。详细用法可以参考JDK的API文档

    技术图片

四.AIO简单分析

  AIO(NIO2.0)和 NIO的主要区别是前者是异步的,而后者是同步的。

  同步:
    所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是事情必须一件一件地做,等前一件做完了才能做下一件事。
  异步:
    异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

   与NIO不同,当进行读写操作时,AIO只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。  即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。

以上是关于Java BIO与NIO以及AIO分析的主要内容,如果未能解决你的问题,请参考以下文章

从底层入手,图解 Java NIO BIO MIO AIO 四大IO模型与原理

详解 同步异步阻塞非阻塞 与 BIO NIO AIO区别多路复用

java-----NIO总结

Netty学习(源码分析)

Netty学习(源码分析)

BIO,NIO,AIO详解