Amazing Arch架构

快速掌握NIO(上)

2018-10-08  本文已影响209人  叩丁狼教育

本文作者:禹明明,叩丁狼高级讲师。原创文章,转载请注明出处。

NIO概述

NIO是JDK1.4引入的新的IO模型,是New I/O的简称,现在更多人认为应该是 Non-blocking(非阻塞) IO的简称,NIO提供了比传统IO更高的性能和更优的操作方式

JDK1.4之前我们使用的IO是同步阻塞的,我们可以称之为BIO(阻塞IO)
JDK1.4Java学习了Linux的select模式提供了新的同步非阻塞IO模式NIO(非阻塞IO)
JDK1.7 的NIO2学习了Linux的epoll模式才是真正实现了(非阻塞异步IO),我们称之为AIO,但是由于AIO用的不多,我们就暂不讨论

JAVA的IO模型提供了标准输入输出(操作文件) 和网络编程两套API。
但是NIO对于标准输入输出的性能提升并没有那么明显(其实IO底层已经使用了NIO的技术重新实现过),但是对于网络编程方面,NIO对性能的提升是非常巨大的,目前非常流行Mina和Netty都是对NIO的一种封装

NIO标准输入输出API

普通的IO我们都非常熟练了,我们来看一个普通IO和NIO复制文件的代码对比


 private long testNIO()throws IOException{
        File src = new File("D:/src.txt");
        File dest = new File("D:/dest.txt");
        long startTime = System.currentTimeMillis();

        if(!dest.exists())

            dest.createNewFile();

        RandomAccessFile read =new RandomAccessFile(src,"rw");

        RandomAccessFile write =new RandomAccessFile(dest,"rw");

        FileChannel readChannel = read.getChannel();

        FileChannel writeChannel = write.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024*1024);//1M缓冲区

        while(readChannel.read(byteBuffer) >0) {

            byteBuffer.flip();//翻转状态,从写模式切换到读模式(必须!)

            writeChannel.write(byteBuffer);

            byteBuffer.clear();//重置Buffer位置,相当于清空Buffer,但是只是改变位置指向,不真正删除数据

        }

        writeChannel.close();
        readChannel.close();
        long endTime = System.currentTimeMillis();

        return endTime - startTime;

    }

    public void testIO(){
        File src = new File("D:/src.txt");
        File dest = new File("D:/dest.txt");

        try (
                InputStream in = new FileInputStream(src);
                OutputStream out = new FileOutputStream(dest);
        ) {
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这部分我们学习一下怎么使用就行,不做过多讨论。通常当我们提到NIO的时候更多关注的是网络通信部分

网络编程API

要了解NIO我们需要对比一下传统 BIO 网络通信模型和 NIO通信模型的区别

先了解一下NIO的三个核心概念:

缓冲区 Buffer

传统IO是面向stream的,NIO是面向缓冲区(Buffer)的
Buffer是一个对象,包含一些要写入或者读出的数据。
在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的.在写入数据时也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。最常用的就是ByteBuffer, 他们实现了相同的接口:Buffer。

Buffer中比较重要的4个属性:position、limit、capacity、mark. 在使用 Buffer 时,我们实际操作的就是这四个属性的值.
具体介绍下4个属性:

image.png

通道 Channel

我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。

底层的操作系统的通道一般都是全双工的,所以全双工的Channel比流能更好的映射底层操作系统的API。

Channel主要分两大类:

多路复用器 Selector

Selector是Java NIO 编程的基础。
Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。

再来对比一下NIO的模型


image.png

阻塞IO的问题主要体现在三个方面:

  1. 创建大量线程,浪费内存
  2. 由于是阻塞模式,线程在等待任务处理完成的时间都是阻塞的,没有任何意义
  3. 线程太多,就需要CPU在多个线程之间进行切换,浪费大量CPU时间

而NIO的流程并没有为每一个client都去创建一个线程,而是使用了一个Selector来轮询已经准备就绪的key(先简单理解为一个key对应一个Channel),这样就可以节省大量的线程和线程切换的开销而且不会对性能造成太大影响,尤其在多线程高并发的时候,NIO的性能也不会像IO一样出现急剧下降甚至宕机。

拿一个买票例子来说IO和NIO的区别就是:
IO就相当于每个人(线程)都排几百米的队,自己去买票,而卖票(任务处理)的速度是一定的,排队期间你(线程)做不了任何事情(阻塞),这样不但无法提高卖票效率,反而容易造成大量拥堵
NIO就是找黄牛买票,黄牛买到了打电话通知你,而这个黄牛(Selector)同时在为成千上万个需要买票的人(Channel)服务,哪个票能买了(就绪),黄牛马上能够知道,然后就去找到对应的客户(Chanel)去处理

但是NIO缺点也不是没有,那就是API比较复杂,学习成本较高,不好维护。所以如果是并发量不高的简单服务最好还是使用传统IO,方便维护。而对于高并发的系统最好采用NIO,但是一般也不会直接使用NIO的原生API,而是使用NIO框架Mina或者Netty

想获取更多技术视频,请前往叩丁狼官网:http://www.wolfcode.cn/openClassWeb_listDetail.html

上一篇下一篇

猜你喜欢

热点阅读