快速掌握NIO(上)

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

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缓冲区
  • Channel管道
  • Selector选择器

缓冲区 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个属性:

  • capacity(容量):一个buffer能够容纳数据元素的最大数量,capacity不会为负数,且永远不能被改变.
    假设:IntBuffer.allocate(1024), 分配了大小为1024的元素个数,则capacity就等于1024.
  • limit(上界):一个buffer的limit指的是第一个不能在读也不能在写的元素索引. limit不会为负数,并且一定是小于capacity的.
    假设:IntBuffer.allocate(1024), 我们在程序中设置limit=512,说明Buffer的容量是1024,但是从512之后既不能读也不能写了,进一步说明该Buffer的实际可用大小是512.
  • position(位置):一个buffer的position指的是下一个将要读或者写的元素的索引.position不会为负数,并且一定是小于limit的. position的位置主要由get()和put()方法的调用来更新.
  • mark(标记):一个备忘地址,作为临时标记位置使用,标记在设定前是未定义的.
    mark的使用场景:
    假设IntBuffer.allocate(1024),现在position位置为10,现在只想发送512到1024之间的缓冲数据,此时我们可以buffer.mark(buffer.position())既将position记入mark位置,然后buffer.postion(512),此时发送的数据就是512到1024之间的数据。发送完成后,调用buffer.reset()将mark临时标记赋值给position使得position=mark。注意如果未设定mark,而调用了buffer.reset()方法则会抛出InvalidMarkException.
    不变式:
    0 <= mark <= position <= limit <= capacity
image.png

通道 Channel

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

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

Channel主要分两大类:

  • SelectableChannel:用户网络读写
  • FileChannel:用于文件操作

多路复用器 Selector

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

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

  • 下面是普通的BIO通信模型


    image.png

    采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。Blocking-IO是典型的一请求一应答的模型,每一个Socket的处理过程当中都是阻塞的。
    BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程来处理这条链路,在需要满足高性能、高并发的场景是没法应用的(大量创建新的线程会严重影响服务器性能,甚至罢工)。

再来对比一下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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容