[译]Java NIO vs. IO

原文地址:https://dzone.com/articles/java-nio-vs-io

在学习Java NIO 和 IO API's 的时候,一个问题在我脑海闪过:
我什么时候应该用 IO而什么时候应该用NIO呢?
在本文,我会尝试带来关于Java NIO与IO区别的一些启发,它们的使用场景,还有它们会怎样影响你的代码设计。

Java NIO 与 IO的主要区别

下表简单说明Java NIO和IO的主要区别。我会针对表中每个不同点在下面的章节展开详细说明。

IO NIO
面向流 面向缓冲区
阻塞IO 非阻塞IO
选择器

面向流 vs. 面向缓冲区

Java NIO 与 IO 第一个较大的不同点在于,IO 是面向流的,而NIO则是面向缓冲区。那么,这究竟是什么意思?
Java IO 面向流意味着你每次从一个流中读一个或多个字节。你怎么处理读取的字节取决于你。他们没有任何地方可以做缓存。此外,你不能在流的数据中来回移动。如果你需要在数据中来回移动,你还需要先把它缓存到缓冲区。
Java NIO的面向缓冲区的方法稍微不同。数据是读进缓冲区随后被处理的。只要你需要,你可以在缓冲区来回移动。这会给你在处理的时候更多的灵活性。然而,在全部处理完后你仍需要检查缓冲区是否包含你需要的全部数据。然后,你需要保证在读入更多数据到缓冲区的时候,不会覆盖你在缓冲区还没处理好的数据。

阻塞 vs. 非阻塞IO

Java IO的各种各样的流都是阻塞的。这意味着,当一个线程调用 read() 或 write()的时候,线程会一直阻塞直至数据全部读完或全部写完。此线程在这个时候不能做其他任何事。
Java NIO的非阻塞模式,允许一个线程在当前channel中有可用的数据从Channel中获取数据,如果当前的数据不可用,则不获取。而不会阻塞在这里直至数据可用,这个线程能继续去做其他事情。
非阻塞写也是如此。一个线程可以将数据写进channel,而无需等待它全部写完。线程能继续去做其他事情。
在非阻塞IO调用情况下,线程花费闲置时间通常用于在其他channel执行IO。这就是为什么一个线程可以处理多个channel的输入和输出。

选择器

Java NIO的选择器允许单线程监控多个输入的channel。你可以在一个选择器上注册多个channel,然后用单线程去“选择”当前需要处理的channel,或者选择等待处理的channel。选择器机制让单线程管理多个channel变得容易。

NIO 和 IO 怎样影响应用设计

不论你选择 NIO 还是 IO 作为你的工具包,可能影响你的应用设计的一下方面:

  1. API 调用 NIO 或者 IO 的类。
  2. 数据的处理。
  3. 用于处理数据的线程数。

API 调用

当然是API调用NIO还是IO,看起来是不同的。没有什么惊喜。不只是从输入流中由字节到字节读取数据,数据必须先读入缓冲区,然后才能进行处理。

数据处理流程

数据的处理流程同样受到使用纯 NIO 设计还是 IO 设计的影响。
在IO设计中,你是从InputStream 或者 Reader中读取字节。想象你正在处理基于行的文本数据流。例如:

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的内容。类似地,当第二个reader.readLine()调用返回时,你知道这行包含了age 的内容。
如你所见,程序仅仅在有新的数据读取的时候处理,而且每一步你都知道数据是什么。一旦执行线程在代码中已经处理完一段特定的数据,这个线程不会在数据中向后移动(基本不会)。下图阐述了文中说的原理:

Java IO: 阻塞流读取数据

NIO 的实现则不同。以下是简单的例子

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

注意到第二行,读取字节是从channel读取到ByteBuffer。当方法返回时,你不知道是否你需要的全部数据都已经在缓冲区。你所知的只是缓冲区中包含了一些字节。这使得编程变得困难。
想象一下,如果在第一个 read(buffer)调用后,读入缓冲区的数据只是读了半行。例如:“Name: An”。你可以处理数据吗?不见得。你需要等到整行都读入缓冲区,否则处理数据是没有意义的。
那么你怎样知道缓冲区是否已经包含足够的数据,使得处理是有意义的呢?好吧,你不知道。唯一方法找出这个,就是去查询缓冲区中的数据。结果就是,你可能需要多次检查缓冲区的数据直到全部的数据已经在里面。在程序设计当中这样效率低下而又容易造成混乱。例如:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

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

bufferFull() 方法可以跟踪多少数据读进缓冲区,并且根据缓冲区是否已经满,返回 ture 或者 flase。换句话说,当缓冲区满 的时候,它就准备好被处理了。
bufferFull() 方法扫描缓冲区,但必须让缓冲区保持与调用bufferFull()方法之前相同的状态。否则,下一个数据被读进缓冲区的位置可能是不正确的。这不是不可能的,不过这是另一个需要当心的问题。
如果缓冲区是慢的,那么它可以被处理。但如果它还没满,你也可以部分处理在那里的数据,在特定的情况下这也是合理的。大部分情况则不是。
以下是 数据是否在缓冲区准备好的原理图:


Java NIO:从channel中读取数据直到全部数据已进入缓冲区

总结

NIO 允许你用单(或多)线程管理多个channel(网络连接或文件),但处理数据的成本比从阻塞流读取数据更高。
如果你需要同时处理数千个打开的连接,而仅仅是发送少量的数据,比如一个聊天服务,那么使用NIO实现可能更合适。同样地,如果你需要保持打开多个与其他电脑的连接,例如在P2P网络,用单线程去管理
你的出站网络,也是有优势的。以下是,单线程,多连接的原理图:


Java NIO: 单线程管理多个网络

如果你只有少数连接而且需要很高的带宽,同一时间发送大量数据,可能经典的IO服务实现会是最合适的。以下是经典IO服务设计的原理图:


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

推荐阅读更多精彩内容