Java NIO vs. IO

在我们学习 NIOIO 的 API 时,脑海中会冒出这个问题:
“什么时候用 NIO?什么时候用 IO?”
接下来我们将对二者之间的异同点进行详述。

Java NIO 与 IO 之间的主要差异

下面的表格列出了二者间的主要差异,下文将对这些差异进行详述。

IO NIO
面向 Stream 面向 Buffer
阻塞 IO 选择器 Selectors

面向 Stream vs. 面向 Buffer

面向 stream 和 面向 buffer 的不同,意味着什么呢?

Java IO 是面向流的,意味着你从流中一次读取一个或多个字节,你用读到的字节做什么完全取决于你,字节没有任何缓存。此外,你无法在流中前后移动。如果你需要在你从流中读到的数据里前后移动,那你必须先将它们缓存在缓冲区中。

Java NIO 面向缓冲区的方法略有不同。数据被读到之后处理的缓冲区中。你可以在缓冲区中按你的需求前后移动。这会让你在处理期间有更大的灵活性。然而,为了完整的处理数据,你需要检查缓冲区是否包含了你需要的全部数据。还有,你需要确保往缓冲区写入更多数据时,是否会覆盖缓冲区中待处理的数据。

阻塞(Blocking)vs. 非阻塞(Non-blocking) IO

Java IO 的各种流都是阻塞式的。也就是说,当一个线程调用 read()write() 方法,那个线程将被阻塞,直到有数据读到或数据完全写入为止。该线程在此期间将什么都不做。

Java NIO 的非阻塞模式使一个线程能够请求从通道中读数据,仅得到当前可读的数据,或者如果当前没有数据可读时,就什么都得不到。而不是一直阻塞到数据成为可供读取的状态,在此期间线程可以继续做其他事情。
该方式同样适用于非阻塞式写入。一个线程可以请求向通道中写入一些数据,但是不必等到它完全写入。在此期间线程可以继续做其他事情。

该线程在非阻塞 IO 调用期间,会利用空闲时间处理其他通道的 IO 请求。也就是说,单个线程现在可以管理多个通道的输入和输出。

选择器(Selectors)

Java NIO 的 选择器(selectors )可以用单个线程来监听多个通道(channels )的输入状态。你可以在一个选择器上注册多个通道,然后用单个线程去选中(select)那些有输入信息的通道或准备进行输出操作的通道来处理。这个选择器机制使得单线程管理多通道的问题变得很简单。

NIO 和 IO 对应用程序设计的影响

无论你选择 NIOIO 作为你的 IO 工具,都可能从以下几个方面影响应用程序的设计:

  1. NIOIO 类的API 调用方式;
  2. 数据处理过程;
  3. 用于处理数据的线程数。

API调用

采用 IO 来调用API,仅需要从输入流(例如 InputStream)中读取字节数据,而 NIO 方式则需要先将数据读取到一个缓冲区(Buffer)中,然后在缓冲区中进行处理。
由此来看,NIOIO 在 API调用方面差距较大。

数据处理

当使用一个纯 NIOIO 方式时,对数据处理过程也有一定的影响。

IO 方式,你可以从 InputStreamReader 中读取数据字节。假设你正在处理一个基于行的文本数据流,文本如下:

Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890

这个文本行的流可以这样处理:

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() 会一直阻塞到整行文本读取完成。你也知道这行文本中包含了name 信息。同理,当第二行 readLine() 调用返回时,你知道这行文本中包含了 age 信息。

正如你所看到的,只有当有新数据读取时,程序才会进行,并且每一步你都都知道数据是什么。一旦执行中的线程已经执行了读取某个数据片段的代码,这个线程将无法(几乎不能)回退数据。这个原理在下图中有所说明:

**Java IO: 从阻塞流中读取数据**

NIO 的实现看起来会有所不同,这是一个简单的例子:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);

注意第二行,从通道将字节读入到 ByteBuffer 中。当方法调用返回时,你不知道你所需的所有数据是否已在缓冲区中。这使得处理过程稍微更难一些。

想象一下,在第一次 read(buffer) 调用之后,读入缓冲区的所有内容都是半行。例如,"Name: An"。你能处理这些数据吗?显然是不能的。你需要等到至少有一整行数据进入到了缓冲区,在此之前处理任何数据都无意义。

那么你怎么知道缓冲区是否包含足够的数据来使它有被处理的意义呢?的确,你不会知道。查看缓冲区中的数据是知道的唯一方法。结果是,你可能必须多次检查缓冲区中的数据,然后才知道是否所有数据都在内部。这种方式效率很低,而且在程序设计方面可能变得很乱。例如:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);

while(! bufferFull(bytesRead) ) {
    bytesRead = inChannel.read(buffer);
}

bufferFull() 方法必须跟踪读取到缓冲区中的数据量,并返回 true 或 false,具体取决于缓冲区是否已满。换句话说,如果缓冲区准备进行处理,则认为缓冲区已满。

bufferFull() 方法扫描缓冲区,但必须使缓冲区处于与调用 bufferFull() 方法之前相同的状态。否则,读入缓冲区的下一个数据可能不会在正确的位置读取。这并非不可能,但它也是另一个值得注意的问题。

如果缓冲区已满,则可以进行处理。如果它不是满的,你也许能够部分处理这些数据(假设这在你的特定情况下是有意义的,事实上在大部分情况下是没有意义的)。

在下图中说明了缓冲区内数据准备循环的过程:

**Java NIO: 从通道读取数据,直到所有需要的数据都在缓冲区中**

总结

使用 NIO ,你可以用一个(或少量)线程来管理多个通道(网络连接或文件)。成本是,解析数据可能比在从阻塞流读取数据时要复杂得多。

如果你需要管理成千上万个同时打开的连接,而且每个连接上只发送少量数据(比如聊天服务器),那么采用 NIO 方式来实现服务器会更好。
同样,如果你需要与其他电脑保持大量打开的连接(例如 P2P 网络),使用单个线程管理所有出站(outbound)连接可能是一个优势。下面这个图描述了单个线程管理多连接的设计:

**Java NIO: 单个线程管理多个连接**

如果你需要少量连接数且非常高的带宽,每次发送大量数据,那么采用典型的 IO 服务器实现更符合需求。这个图展示了典型的 IO 服务器设计:

**Java IO: 典型 IO 服务器设计—— 一个线程处理一个连接**

原文地址:http://tutorials.jenkov.com/java-nio/nio-vs-io.html

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

推荐阅读更多精彩内容