【Java NIO 简例】NIO vs IO

阅读更多

原文:《Java NIO vs. IO》

当学习Java NIO 和 IO 的API时,很快会遇到一个问题:
什么时候用IO,什么时候用NIO?

我会尝试在本文提供一些关于Java NIO 与 IO 的不同点、使用案例、及它们将如何影响代码设计方面的见解。

 

NIO 与 IO 的主要不同

下表总结了Java NIO 和 IO 的主要不同。我会在后文详细论述每一个不同点。

IO NIO
面向 Stream 面向 Buffer 
阻塞式IO 非阻塞IO
  Selector

 

面向Stream vs 面向Buffer

NIO 和 IO的第一个最大不同是:IO面向Stream,NIO面向Buffer。这意味着什么?

 

IO 面向 Stream 意味着你可以从stream一次读取一个或多个字节。读多少个字节取决于你。它们不会被缓存在任何地方。进一步而言,你无法在stream数据流中往前或往后移动。如果你需要在这些数据中往前或往后移动,你需要先将其缓存在一个buffer中。

 

NIO 面向 Buffer 稍微有点不同。数据被读到一个buffer中,然后再被处理。你可以根据需要在这个buffer中往前或往后移动。这给了你更多处理过程中的扩展性。但是你也需要检查buffer是否包含了进行完整处理所需的所有数据。而且你需要确保往buffer读入更多数据时不会覆盖掉那些尚未被处理的数据。

 

阻塞 vs 非阻塞

Java IO 的各种Stream是阻塞式的。这意味着,当线程调用了 read() 或 write() 方法,该线程会被阻塞,直到有数据可读,或所有数据被写入。在此期间,该线程无法做其它任何事情。

 

Java NIO 的非阻塞模式允许线程从channel请求数据,且只会得到当前就绪的数据;如果当前没有就绪的数据,就得不到任何数据。且该线程不会被阻塞直到有数据就绪可读,而是会理解返回并可以做其它事情。

非阻塞写数据也类似。线程可以请求向channel写入数据,但不会被阻塞直到所有数据被写入。线程可以继续做其它事情。

线程处于空闲未被阻塞于IO调用时,通常可以处理其它channel的IO。也就是说现在单线程可以处理多channel的输入和输出。

 

Selector

Selector 允许单线程监视多个 Channel。你可以将多个 Channel 注册到一个 Selector 上,让后用单个线程“选出”就绪可读或可写的 Channel。此 Selector 机制使得单线程管理多Channel更容易。

 

NIO 和 IO 如何影响应用设计

选择 NIO 或 IO 会在以下几个方面影响你的应用设计:

  1. 调用的API(来自NIO的类还是IO的类)
  2. 处理数据的过程
  3. 处理数据的线程数

调用的API

毫不意外,使用NIO与IO所调用的API是不同的。在NIO中,数据必须先被读到一个Buffer中,然后再处理;在IO中,数据是被直接从Stream读出来进行处理的。

 

处理数据的过程

IO模式

代码示例:

// InputStream input = ...
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String line1 = reader.readLine();
String line2 = reader.readLine();

【Java NIO 简例】NIO vs IO_第1张图片
 

NIO模式

代码示例:

ByteBuffer buffer = ByteBuffer.allocate(1024);
int readByteCount = channel.read(buffer);

while (!isDataComplete(readByteCount)) {
  readByteCount = channel.read(buffer);
}

channel.read(buffer) 所读取到的数据量是不一定的,可能这些数据并不足以用于后续业务的执行,而需要多次执行读取操作以获得义务意义上完整的数据。
这就引入了低效的数据完整性检查操作,及凌乱复杂的程序设计:

  • isDataComplete(int) 方法用于检查数据是否足以用于后续业务执行;
  • while 循环直到获得足够的数据量。

【Java NIO 简例】NIO vs IO_第2张图片
 

总结

NIO 允许你用单个(或更多)线程管理多个 Channel (如,网络连接 或 文件)。但是其代价是更复杂的数据解析操作(相比与从阻塞Stream中读取数据)。

 

# 如果你需要同时管理上千个连接,且这些连接都只发送少量的数据(如,聊天系统的服务端),那么用NIO实现服务端可能会有优势。
同样的,如果你需要保持大量与其它机器的连接(如,P2P网络),那么用单个线程管理所有向外的连接可能有优势。
这个 单线程-多连接 的设计简化如下:

【Java NIO 简例】NIO vs IO_第3张图片
 

 

# 如果连接较少,且都占用较高的带宽,单次传输的数据量非常多,也许一个典型的IO服务端实现最合适。设计简化如下:

【Java NIO 简例】NIO vs IO_第4张图片

  • 【Java NIO 简例】NIO vs IO_第5张图片
  • 大小: 24.8 KB
  • 【Java NIO 简例】NIO vs IO_第6张图片
  • 大小: 17.9 KB
  • 【Java NIO 简例】NIO vs IO_第7张图片
  • 大小: 16 KB
  • 【Java NIO 简例】NIO vs IO_第8张图片
  • 大小: 23.2 KB
  • 查看图片附件

你可能感兴趣的:(nio)