如何选择Java NIO与IO?

30

我们知道,如果我们想要使用传统的IO来构建服务器,它必须在某个地方阻塞,所以我们不得不使用循环或一个线程一个套接字模式,因此NIO似乎是更好的选择。那么我想知道,NIO是否永远是更好的选择?


4
你是指旧版IO(Java 7之前的版本)还是新的时髦的重新命名的非阻塞IO(http://docs.oracle.com/javase/7/docs/technotes/guides/io/enhancements.html#7)? - RNJ
7个回答

28

在我看来,阻塞式IO通常是最简单易用的选项,除非您的系统有特定要求需要更多功能,否则应该坚持使用最简单的选项。

下一个最简单的选项是阻塞NIO,如果我想要比IO更高效或有更多控制权,我经常会选择它。 它仍然相对简单,但允许您使用ByteBuffers。例如,ByteBuffers支持小端。

一种常见的选择是使用带有Selectors的非阻塞NIO。 这引入的大部分复杂性可以由像Netty或Mina这样的框架处理。如果您确实需要非阻塞IO(例如因为每个服务器有数千个并发连接),建议您使用此类库。 在我看来,如果您拥有数千个连接,则应考虑增加更多服务器,除非每个连接所做的事情相当琐碎。据我所知,谷歌选择更多服务器而不是每个服务器数千个用户。

更极端的选择是使用NIO2。这比非阻塞NIO甚至更加复杂和冗长,我不知道有哪些框架能够很好地支持它。也就是说,当您使用时,它实际上更快。据我所知,如果您拥有Infiniband(这是它设计支持的内容),则值得使用,但如果您拥有以太网,则可能不值得使用。


1
感谢您的帖子。我因为正在研究同一主题而看到了您的回答。我正在尝试理解nio和nio2对Java程序员暴露的操作系统改进,其中包括DMA和内存映射文件等几个方面。您是否还了解更多类似的改进? - Andy Dufresne
2
@MarounMaroun 这取决于消息的大小,但我建议使用一些缓冲区,无论您做出什么其他选择,您都应该能够处理数十万条每秒的消息。请注意:如果可能,请尽量保持消息较小。 - Peter Lawrey
@MarounMaroun 在这种情况下,是的。你达到了哪个限制?有可能你正在读取一个不完整的消息。字节流只能理解字节,而不是消息,因此当你从流中读取时,你可以得到任何一部分消息,甚至只有一个字节。你是如何处理这个问题的? - Peter Lawrey
1
@MarounMaroun 你可以这样做,但队列的目的是让你看到自己正在落后,并可能采取纠正措施。如果长期无法跟上,更多的缓冲甚至队列本身也无济于事。缓冲有助于应对活动突发性增加,短时间内无法跟上但长期来看可以。它可以平滑处理活动突发情况。 - Peter Lawrey
2
@PeterLawrey 小更新,也许可以帮助其他访问者。问题的一部分是机器本身(我的应用程序部署在 AWS t2.small 机器上,该机器的网络性能不佳),将应用程序移动到更高级的机器后,错误数量显著减少了。 - Maroun
显示剩余5条评论

15
如果你想使用非阻塞 IO,NIO 是 Java 中不仅是更好的选择,而且是唯一的选择。但请记住,人们仍然经常使用旧的 IO,因为它编码起来更简单。 NIO API 相当基础,更像是一个支持底层技术的API,而不是客户端API。我建议使用提供更简单接口的API来解决你想要使用非阻塞IO解决的问题。

如果我调用Files.exit(/path/to/nfs/file),并且由于某些NFS挂载问题导致NFS文件过期,那么这个调用不会阻塞,对吗? - AsadSMalik

2

虽然晚了一点,但我个人即使是对于常规的“日常”文件处理,也使用NIO。因此,我使用以下内容:

 1. if(Files.notExists(path)) { } 
 2. Files.createDirectory(path);
 3. Files.newInputStream(path) targetPath.resolve("somefile.txt");
 4. Files.newBufferedWriter(path, charset);
 5. DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path);

对我来说这很好用。由于有relativize或resolveSibling等方法,我更喜欢Path而不是旧的File。

我觉得它并不比IO更复杂。


1
仍然是阻塞的。 - caeus

2
你只有在能够证明使用NIO引入的复杂性是合理的情况下才会使用它。如果你没有关于预期负载的任何指导,也没有关于产品/项目是否具备维护相关代码的资源,那么你应该慎重考虑并使用IO。
为了让我的答案更有说服力,我刚刚花了三个月的时间来维护和修复一个集成层,在这个层中使用了原始的Java NIO(即没有使用任何总体框架)。设计的基本思想是客户端线程将消息添加到队列中,少量的工作线程进行NIO操作,然后以事件驱动的方式将回复传递回客户端线程。回顾起来,我无法证明最初使用NIO的决定是合理的,因为它成为了一个分散注意力的干扰因素,浪费了大量本应用于高级业务逻辑的时间。

1

NIO.2 API相对于旧的java.io.File类用于文件操作方面有以下优势:

  • 支持特定于文件系统的属性。
  • 允许您直接遍历目录树。
  • 支持符号链接。

有关具体用例和更多详细信息,您可以查看this文章。


1
你可以使用任何一种方法,除非你要创建“超快速”服务器。
当然,在这里使用nio是一个好的选择,因为它是编写高吞吐量多客户端服务器的新型现代方式。

1
值得商榷。有很多研究表明相反的结果。新颖并不总是一种改进。 - user207421
1
@EJP同意- 我对NIO的一些测试实际上变慢了! - RNJ
@EJP 没错,您说得完全正确,但这里毫无疑问。如果作者要为自己的需求实现一个服务器,该服务器不会覆盖超过100个客户端,那么就没有必要使用nio。传统的方式比非阻塞异步模型更简单易懂。 - Max
@MyNameIsTooCommon这个名字没有什么意义。另外一个问题是,如果你正在使用nio来完成适当的任务。 - Max
@Max 如果我没错的话,但是后面的部分我不是很理解。 - user207421

0

传统IO是容易和简化的代码,NIO更加复杂但更加灵活。 在我的情况下,我更喜欢使用IO进行小流式处理,而使用NIO进行大流式处理,但是nio确实更加复杂。

使用NIO时,我必须创建一个完整的包来管理它,而使用IO包则可以直接使用片段。


1
我不同意。看这个:Files.readAllLines(Paths.get(filename), Charset.forName("UTF-8"));。在一行中,你已经解析了文件并将所有内容放入列表中;而在旧的IO中,这将是一个类长的练习。 - Evil Washing Machine
嗯,@EvilWashingMachine,我认为你是对的 :) 但是...如果我们正在讨论解析文件并获取信息,无论如何使用您的方法,您都将进行“旧IO中的类长练习”^^ - Andrea Bori

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接