前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,可是Java 为何又要引入NIO,这是我一直不是很清楚的?前面也只是简单说起了一下:由于性能,可是仅仅是由于性能吗,除此以外是否还有别的缘由,或者说既然NIO性能好,那为何如今咱们还在使用IO。本节咱们就来详细对比一下二者的特性以及二者之间的不一致对咱们编码所带来的影响。缓存
一样,本文会主要围绕下面几个方面来总结: 服务器
两种IO的各自适用场景socket
总结工具
二者之间的不一样主要体如今以下三个方面:性能
在接下来的部分,咱们逐个讨论这三个不一样。学习
Java NIO和IO之间第一个不一样点是IO是面向流(Stream)的而NIO是面向缓冲区(Buffer)的。开发工具
Java IO是面向流的,这意味着是一次性从流中读取一批数据,这些数据并不会缓存在任何地方,而且对于在流中的数据是不支持在数据中先后移动。若是须要在这些数据中移动(为何要移动,能够屡次读取),则仍是须要将这部分数据先缓存在缓冲区中。编码
而Java NIO采用的是面向缓冲区的方式,有些不一样,数据会先读取到缓冲区中以供稍后处理。在buffer中是能够方便地前移和后移,这使得在处理数据时能够有更大的灵活性。可是呢须要检查buffer是否包含须要的全部数据以便可以将其完整地处理,而且须要确保在经过channel往buffer读数据的时候不可以覆盖还未处理的数据。
Java IO中使用的流是属于阻塞式的,意味着当线程调用其read()或write()方法时线程会阻塞,直到完成了数据的读写,在读写的过程当中线程是什么都作不了的。
Java NIO提供了一种非阻塞模式,使得线程向channel请求读数据时,只会获取已经就绪的数据,并不会阻塞以等待全部数据都准备好(IO就是这样作),这样在数据准备的阶段线程就可以去处理别的事情。对于非阻塞式写数据是同样的。线程往channel中写数据时,并不会阻塞以等待数据写完,而是能够处理别的事情,等到数据已经写好了,线程再处理这部分事情。
当线程在进行IO调用而且不会进入阻塞的状况下,这部分的空余时间就能够花在和其余channel进行IO交互上。也就是说,这样单个线程就可以管理多个channel的输入和输出了。
Java NIO中的Selector容许单个线程监控多个channel,能够将多个channel注册到一个Selector中,而后能够"select"出已经准备好数据的channel,或者准备好写入的channel。这个selector机制使得单个线程同时管理多个channel变得更容易。
选择使用NIO仍是IO做为开发工具包会在以下几个方面影响应用设计:
采用NIO的API调用方式和IO是不同的,与直接从InputStream中读取字节数据不一样,在NIO中,数据必需要先被读到buffer中,而后再从那里进行后续的处理。
采用NIO的设计仍是IO的设计,数据的处理方式也是不同的。
在IO设计中是从InputStream或Reader中逐字节读取数据。在下面例子中,咱们经过一个处理基于文本的简单例子来讲明两种设计的区别:
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890
采用IO的方式,这些数据流会像下面这样处理:
InputStream input = ... ; // get the InputStream from the client socket BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String nameLine = reader.readLine(); String ageLine = reader.readLine(); String emailLine = reader.readLine(); String phoneLine = reader.readLine();
注意在这里处理状态是经过程序执行了多少就可以肯定的。换句话说,当第一行reader.readLine()返回以后,能够肯定已经读了一整行。由于readLine()会阻塞直到整行数据读完。并且咱们可以确切地知道所读取的这第一行是包含名字的。相似,第二次调用readLine()返回以后咱们确切地知道所读取的内容包含年龄。
能够知道,上面的程序只有当有新的数据是可读时才会进行处理,在每一步都知道数据是什么。一旦执行读写的线程已经读取了一些数据以后,是不可以再返回到前面的数据(由于流的方式只能读取一次,很好理解,像水同样,流完了就流完了,除非你把它装到容器里面)。上面程序中所遵循的原则以下图所示:
而NIO的实现则看起来有些不一样,以下:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer);
注意第二行是从channel读取数据到buffer中,当read()方法返回时咱们是不知道是否全部须要的数据有没有所有读到buffer中,咱们知道的只是buffer中可能包含一部分数据,这会使得整个过程的处理有点麻烦。
假设,在第一次调用read()以后,全部读到buffer中的数据只有半行,好比,"Name:An"。这时能够处理数据吗,显然是不能够的(由于尚未读完),须要等到至少一行数据被读到buffer中。
那么咱们又如何来知道buffer中包含足够能够处理的数据呢?惟一的办法只有检查buffer中的数据了。因此结果就是咱们须要经过屡次检查buffer中的数据来判断数据是否已经所有读进buffer了。这样就很低效,并且容易致使程序设计混乱。好比:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { bytesRead = inChannel.read(buffer); }
bufferFull()方法会跟踪有多少数据被读到buffer中了,而且返回true或者false,取决于buffer是否已满。换言之,若是buffer中的数据已经可供处理,那就表明它已经满了。
bufferFull()方法会扫描整个buffer,要保证扫描并不会影响整个buffer的状态,否则可能致使后面要读入buffer中的数据不能读到正确地位置。这并不是不可能,因此对于设计者来讲这是一个须要关注的地方。
若是buffer已满,那其中的数据就可供处理。若是没满,那可能须要部分地处理那些数据(若是须要的话),只是在大部分场景下是不须要的。
下图描述了这种 is-data-in-buffer-ready的循环:
NIO使得经过单个或少许线程来管理多个channel(网络链接或者文件)成为可能,可是代价是传递数据会比从阻塞的流中读数据更复杂。咱们学习一项新的技术时,既要看到其优势也要看到其缺点。
若是须要同时管理数以千计的链接,并且每一个链接只会发送少许的数据,好比聊天服务器,用NIO的方式来实现这个服务器则比较合适。相似的,若是须要长时间保持一些和别的电脑的链接,好比在一个P2P网络中,用单个线程来管理全部的对外链接也有优点。以下图描述了这种单个线程,多个链接的设计模型:
若是只有少许的链接,可是每一个链接又都占用大量的带宽,短期以内发送大量数据,这时后也许传统的IO模型会更适用,由于专注,因此在特定场景下能够更高效。以下图描述了一个基于传统IO模型设计的服务器模型:
在前面总结了不少IO和NIO的相关知识以后,本文总结了Java中两种IO类库的区别即各自的优缺点:
技术没有好坏,只有合适与否!