NIO vs. IO

在学习Java NIO和IO时,一个问题很快浮现在脑海里:

我应该嘛时候用IO,又嘛时候用NIO呢?

在本文中,我将试图阐明Java NIO和IO的不同,它们的使用场景,以及它们如何影响你的代码设计。

Main Differences Betwen Java NIO and IO

下面的表格总结了Java NIO和IO的主要不同点。我会在表格后面的章节中详细阐述每一个不同点。

IO NIO
面向流的 面向Buffer的
阻塞IO 非阻塞IO
Selectors

Stream Oriented vs. Buffer Oriented

Java NIO和IO的第一大不同就是IO是面向流的,而NIO是面向Buffer的。那么,这意味着什么呢?

Java IO面向流意味着你每次从流中读取一个或多个字节。至于这么处理读取出来的字节就取决于你个人了。这些数据不会被任何东西缓存起来。进一步说,你不能在流中进行前后的移动。如果你想在从流中读取数据时候前后移动,那么你需要先先将数据用个Buffer缓存起来。

Java NIO的面向Buffer的方法有些稍微的不同。数据被读入稍后进行处理的Buffer中。你可以在Buffer中进行前后的移动,如果你需要的话。这给你在处理数据时提供了更高的灵活性。但是,在完整的处理数据之前,你还是需要检查Buffer中时候已经包含了所有你需要的数据。并且,你还要确保在往Buffer中读入更多数据的时候,不会覆盖那些还未被处理的数据。

Blocking vs. Non-blocking IO

Java IO中的各种流都是阻塞的。也就是说,当一个线程调用read()write()方法的时候,线程会一直阻塞,直到真正读取到数据,或者全部数据都被写入。该线程在同一时间不能再去干其他任何事。

Java NIO的非阻塞模式允许线程请求从Channel读取数据,并且只能读到当前可用的数据,或者什么也读不到,如果当前没有可用数据的话。不用一直阻塞到有数据可读为止,线程可以去做其他事情。

非阻塞的写也一样。线程可以请求向Channel写入数据,但不用一直等到数据全部写入为止。它可以继续执行其他操作。

当线程的空闲时间没有被IO操作阻塞时,一般都花在了其他Channel的IO上。也就是说,一个线程现在可以管理多个Channel的输入和输出了。

Selectors

Java NIO的Selector允许一个线程监控多个Channel的输入。你可以向Selecotr注册多个Channel,然后只用一个线程来“select”那些已经有输入数据可以处理的Channel,或者“select”那些可以写入数据的Channel。这种Selector机制使单一线程管理多个Channel变得简单。

How NIO and IO Influences Application Design

你选择NIO还是IO来作为你的IO工具包,会在以下几个方面影响你的应用程序设计:

  1. 调用NIO和IO的API类;
  2. 数据的处理;
  3. 用来处理数据的线程数。

The API Calls

当然,调用NIO的API和调用IO的API看起来肯定不一样。这没啥好惊讶的。比起直接从一个比如InputStream直接读取字节数据,使用NIO必须先将数据读入一个Buffer,然后再处理Buffer里面的数据。

The Processing of Data

使用纯的NIO设计或传统IO设计,对数据的处理也有影响。在IO设计中,你从InputStream或者Reader读取字节数据。想象你正在处理一个基于行的文本流,比如:

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

这个文本流可能被这样处理:

InputStream input = ... ; // 从客户端获取流

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()方法返回的时候,你知道年龄就在这一行数据中,等等。

如你所见,程序只会在读到新数据后进行处理,并且每一步你都知道读到的数据是什么。一旦执行线程走过了读取某一段数据的代码,它就会无法再返回读取之前的数据了(大多数情况如此)。该原理如下图所示:
Java IO:从阻塞型流中读取数据

NIO的实现会看起来有些不一样。这里有一个简单的例子:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

注意第二行,从Channel读取数据到Buffer中。当这个方法返回的时候,你并不知道你需要的数据是否已经都在Buffer中了。你知道的只是这个Buffer里有数据。这让数据处理变得有点复杂了。

想象一下,假如,第一次调用read(buffer)后,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,并且根据Buffer里的数据是否已满truefalse。换句话说,如果Buffer里的数据已经够处理了,则认为是满了。

bufferFull()方法会对Buffer进行扫描,但扫描结束后Buffer的状态必须和扫描前一样。如果不一样,后续读入Buffer的数据可能会被放到错的位置。这不是不可能,但却是另一个需要注意的问题。

如果Buffer满了,那就可以被处理了。如果还没满,也也可以做部分处理,如果这对你来说有意义的话。但多数情况下都不行。

检查Buffer里的数据是否足够做处理的循环如下图所示:

Java NIO:从Channel读取数据至所需数据都已读入Buffer

Summary

NIO允许你只使用一个(或少量的)线程管理多个Channel(网络连接或文件),但代价是解析数据比直接从阻塞流读取数据要复杂一些。

如果你需要同时管理成千上万个连接,每个连接只发送少量数据,比如聊天服务器,那么使用NIO来实现可能有优势些。同样的,如果你需要保持很多连接到其他服务器的连接,比如P2P网络,使用单一线程来管理这些出站连接可能更好。这种单线程,多连接的设计如下图所示:
Java NIO:单线程管理多连接

如果你有一些占用很大带宽的连接,每次发送很多数据,可能传统IO是最好的实现方式。传统IO服务器设计如下图所示:
Java IO:传统IO服务器设计 - 一个线程负责一个连接

发现貌似有人在看这个系列文章了,有必要说明下,这个Java NIO系列来源于jenkov.com,本文只是翻译,希望大家千万不要误会,本文不是原创。原文地址:Java NIO

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

推荐阅读更多精彩内容

  • 参考:http://ifeve.com/java-nio-vs-io/原文地址 目录 Java NIO教程 Jav...
    步积阅读 5,116评论 0 44
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,555评论 1 143
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    编码前线阅读 2,268评论 0 5
  • 一个早春的清晨, 我在家重复那亘古不变的家务, 忽然悦耳的鸣声如清泉在石上流动 使任何乐器都黯然失色 我推开门凝视...
    彦蘋阅读 256评论 0 3
  • 今日刷网页和手机时间记录了172分钟 虽然时间特别长,但是最起码记了,还是给自己点个赞吧。 今天走神的时间这么长主...
    告别老妖王阅读 308评论 0 0