目录:
Java NIO 学习笔记(一)----概述,Channel/Buffer
Java NIO 学习笔记(二)----汇集和分散,通道到通道
Java NIO 学习笔记(三)----Selector
Java NIO 学习笔记(四)----文件通道和网络通道
Java NIO 学习笔记(五)----路径、文件和管道 Path/Files/Pipe
Java NIO 学习笔记(六)----异步文件通道 AsynchronousFileChannel
Java NIO 学习笔记(七)----NIO/IO 的对比和总结html
学完 NIO 和 IO 后,有一个问题:何时应该使用 IO,何时应该使用 NIO ?本文将尝试阐明 NIO 和 IO 之间的差别,并提供它们的用例,以及它们对程序代码的设计影响。java
IO | NIO |
---|---|
以 Stream 为导向 | 以 Buffer 为导向 |
阻塞 IO | 非阻塞 IO 选择器 |
NIO 和 IO 之间的第一个重要区别是 IO 是面向流的,其中 NIO 是面向缓冲区的。 那么,这意味着什么?api
面向流的 IO 意味着能够从流中一次读取一个或多个字节,能够按咱们的意愿使用读取的字节。 它们不会缓存在任何地方,此外,没法在流中的将数据先后移动。 若是须要将读取的数据先后移动,则须要先将其缓存在缓冲区中。缓存
NIO 的面向缓冲区的方法略有不一样。 将数据读入缓冲区,稍后处理该缓冲区。 能够根据须要在缓冲区中先后移动。 这使在处理过程当中更具灵活性。 可是,还需检查该缓冲区中是否包含全部须要处理的数据,而且须要确保在将更多数据读入缓冲区时,不会覆盖还没有处理的缓冲区中的数据。服务器
标准 IO 的各类流都是阻塞的。 这意味着当线程调用 read() 或 write () 时,该线程将被阻塞,直到一些数据被读取或者彻底写入,在此期间,线程没法执行任何其余操做。网络
NIO 的非阻塞模式容许线程请求从通道读取数据,而且只获取当前可用的内容,若是当前没有数据可用,就什么都不读取。 线程能够继续作其余事情,而不是在数据可供读取以前保持阻塞状态。
非阻塞写入也是如此。 线程能够请求将某些数据写入通道,但在彻底写入以前不会一直等待它,这样,线程能够在同一时间作继续其余事情。异步
线程在 IO 操做中没有由于阻塞花费等待时间,一般将等待数据准备的时间用在其余通道上执行 IO 操做。 也就是说,单个线程如今能够管理多个输入和输出通道。学习
选择器容许单个线程监视多个输入通道。可使用选择器注册多个通道,而后使用单个线程“选择”具备可用于处理的输入的通道,或选择准备写入的通道。 这种选择器机制使单个线程能够轻松管理多个通道。线程
不管选择 NIO 仍是 IO ,可能都会影响应用程序设计的如下方面:设计
固然,使用 NIO 时的 API 调用看起来与使用 IO 时不一样。由于必须首先将数据从通道读入缓冲区,而后在缓冲区进行处理,而不是仅仅从 InputStream 读取数据字节。
使用纯 NIO 设计是,对比 IO 设计,数据处理也会受到影响。
在 IO 设计中,从 InputStream 或 Reader 中读取字节的数据字节。 想象一下,正在处理基于行的文本数据流。 例如:
Name: czwbig Age: 21
这组文本行能够像这样处理:
InputStream input = ... ; BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String nameLine = reader.readLine(); String ageLine = reader.readLine();
注意处理状态是如何根据程序执行的程度肯定的。 换句话说,一旦第一个 reader.readLine() 方法返回,就肯定已经读取了整行文本,由于 readLine() 阻塞直到读取完整行,还知道此行包含“Name”。 一样,当第二个 readLine() 调用返回时,能够知道此行包含“Age”等。
因此,只有当有新数据要读取时,程序才会进行,而且对于每一个步骤,都知道该读取的数据是什么。 一旦执行的线程已经读取过代码中的某个数据片断,该线程就不会再向后读取旧数据(一般不会)。 下图也说明了此原则:
同上需求,NIO 实现看起来会有所不一样。这里有一个简化的例子:
ByteBuffer buffer = ByteBuffer.allocate(64); int bytesRead = inChannel.read(buffer);
注意第二行从通道读取字节到 ByteBuffer 。 当该方法调用返回时,咱们是不知道所需的全部数据是否都已在缓冲区内的,只知道缓冲区包含一些字节。 这使得处理数据变得困难。
想象一下,在第一次读取(缓冲)调用以后,是否全部读入缓冲区的内容都是半行。 例如,“Name:cz”。 你能处理这些数据吗? 显然不能。 在处理任何数据以前,咱们须要等待至少一整行数据进入缓冲区。
那么怎么知道缓冲区是否包含足够的数据来处理它?惟一方法是查看缓冲区中的数据。 这样将致使:在知道全部数据是否存在以前,可能须要屡次检查缓冲区中的数据(轮询)。 这既低效又可能在程序设计方面变得混乱。 例如:
ByteBuffer buffer = ByteBuffer.allocate(64); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { bytesRead = inChannel.read(buffer); }
bufferFull() 方法必须跟踪读入缓冲区的数据量,并返回 true 或 false ,具体取决于缓冲区是否已满。 换句话说,若是缓冲区已准备好进行处理,则认为它已满。
bufferFull() 方法扫描缓冲区,而且必须使缓冲区保持与调用 bufferFull() 方法以前相同的状态。 若是不这样,则可能没法在正确的位置继续读入下一个数据到缓冲区中。 这不是不可能的,但这是另外一个须要注意的问题。
若是缓冲区已满,则能够对其进行处理。 若是缓冲区还没满,有可能让程序先部分处理已到达的数据,这在的特定状况下是有意义的。 但在许多状况下,不完整的数据没有处理的意义。
这个图中说明了 is-data-in-buffer-ready 循环:
NIO 容许仅使用一个(或几个)线程来管理多个通道(网络链接或文件),但成本是解析数据可能比从阻塞流中读取数据时更复杂一些。
若是须要同时管理数千个打开的链接,每一个只发送一些数据,例如聊天服务器,这在 NIO 中实现服务器多是一个优点。 一样,若是须要与其余计算机保持大量开放链接,例如,在 P2P 网络中,使用单个线程来管理全部出站链接多是一个优点。 下图中说明了这种一个线程,多个链接的设计:
但若是拥有较少带宽的链接,一次链接的数据量较大,那么经典的 IO 服务器实现可能更合适的。 下图说明了这种典型的 IO 服务器设计:
因此,应该根据具体的状况分析,选择更适合的,而不是更新的。