前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java NIO-12.NIO和IO

Java NIO-12.NIO和IO

作者头像
悠扬前奏
发布2019-06-02 12:55:09
7510
发布2019-06-02 12:55:09
举报
文章被收录于专栏:悠扬前奏的博客

学习了Java NIO和IO API之后,就有了一个问题: 什么时候用IO,什么时候用NIO? 本文将试着阐明Java NIO和IO之间使用上的区别,以及它们是如何影响到你的代码设计的。

Java NIO和IO之间的主要区别

IO

NIO

面向流

面向缓冲区

阻塞IO

非阻塞IO

选择器

下面的表格总结了Java NIO和IO的区别。表格后面对更多的细节进行说明。

IO

NIO

面向流

面向缓冲区

阻塞IO

非阻塞IO

选择器

面向流与面向缓冲区

第一个大的区别就是IO是面向流的,而NIO是面向缓冲区的。什么意思呢? Java IO是面向流的,就是说从流中一次性读取一个或者多个字节。无论读取出来的数据怎样使用,它们都不会被缓存。此外,流中的数据也不能被前后移动。如果需要前后移动流中的数据,就需要先将它们存在缓冲区中。 Java NIO的面向缓冲区方式有点不同。数据被读到一个稍后才使用的缓冲区。缓冲区中的数据能根据需要前后移动。这样在处理中提供了很大的灵活性。但是,也需要检查缓冲区中的数据是否包含了需要处理的所有数据。此外,往缓冲区中读取更多的数据时,需要确认没有覆盖掉还未处理的数据。

阻塞和非阻塞IO

Java IO中的各种流是阻塞的。这意味着当一个进程执行读或写的操作时,线程在读到数据或者写入完成之前,都是阻塞地。这期间进程不能进行任何操作。 Java NIO的非阻塞模式使线程能够从通道请求读取数据,仅得到当前可用的部分,如果当前没有数据可用就什么都得不到。而不是在数据可读之前保持阻塞,线程能继续处理其他的事情。 非阻塞写是一样的。线程能请求向通道写入数据,但不会等到完全写入。这期间线程能够处理别的事情。 在IO请求的非阻塞空闲期间,线程通常在处理其他通道的IO。这样,一个线程能够处理多个通道的输入输出。

选择器(Selectors)

Java NIO的选择器让一个线程能够监控多个通道的输出。能够在一个选择器上注册多个通道,然后用这一个线程去“选择”有了要处理的可用数据的通道,或者“选择”准备好写入的通道。选择器简化了单线程控制多通道的工作。

NIO和IO对应用设计的影响

IO工具箱是选择IO还是NIO可能在以下方面影响程序设计:

  1. 调用NIO还是IO类的API。
  2. 数据处理
  3. 处理数据的线程数
API调用

当然使用NIO和IO调用的API看起来不一样。这不意外,因为不是像InputStream那样一个一个字节的读取数据,而是必须先把数据读到缓冲区中,才能进行处理。

数据处理

用纯NIO或IO设计对数据的处理也会有影响。 在IO设计中,使用InputStream或者Reader中逐字节读取数据。假如需要处理行形式的文本数据流,例如:

代码语言:javascript
复制
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890

文本行的流将会被按如下方式处理:

代码语言:javascript
复制
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();

注意处理状态由程序执行多久,换句话说,一旦第一个read.readLine()方法返回,就能肯定文本的整第一行都读取了。readLine()在整行读取完成之前都是阻塞的,这就是为什么能肯定这一行里面包含了姓名。同样的,当第二行readLine()返回,这一行的数据中肯定包含年龄。 可以看到,处理程序仅在有新数据读入时运行,每一步读入的数据都知道是什么。一旦运行中的线程处理了读入的数据,该线程不会回退数据(大多如此)。下图是整个流程:

Reading data from a blocking stream

NIO的实现看起来就不同,例如:

代码语言:javascript
复制
ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

注意第二行把数据从通道中读取到ByteBuffer,当方法返回,并不能知道要处理的数据是否都读取到了缓冲区中,只能知道缓冲区有了一些字节。这使得处理有点困难。 假如,第一次调用read(buffer)只用,所有读到缓冲区中的数据都是半行。例如“Name:An”。能对这样的数据进行处理吗?不一定。需要等至少一整行数据读到缓冲区中,这之前对任何数据的处理都是无意义的。 要知道缓冲区是否包含了足够多的数据使处理变得有意义,只能查找缓冲区中的数据。结果就是,在所有数据读完之前,要对缓冲区中的数据检查好几次。这样效率很低,而且会让程序设计变得混乱,例如:

代码语言:javascript
复制
ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

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

bufferFull()方法必须跟踪有多少数据读入了缓冲区中,返回真或者假,取决于缓冲区是不是满的。换句话说,如果缓冲区准备好处理了,它被认为是满的。 bufferFull()方法扫描整个缓冲区,但是在它返回之前,缓冲区必须保持同样的状态。如果没有,下一次读取到缓冲区中的数据就不能在正确的位置读取。这不是不可能的,但却是又一个需要注意的问题。 如果缓冲区满了,就能被处理。如果不是满的,处理一部分也行的话,那无论数据在不在里面都可以先部分处理。大多数情况下都没意义。

Reading data from a channel until all needed data is in buffer

总结

NIO让一个(或几个)线程可以处理多个通道(网络连接或文件),但是代价就是解析可能比从阻塞的流中读取数据更复杂。 如果同时管理上千个打开的连接,每个连接只发送一点点数据,例如聊天服务器,用NIO实现这种服务器是有优势的。类似的,如果需要保持很多和其他电脑的连接,例如P2P网络,用单个线程处理所有的出站连接,也可能有优势。下图是一个线程处理多个连接的示例图:

A single thread managing multiple connections

如果要处理少量高带宽的连接,一次发送大量的数据,可能传统的IO服务器实现起来合适点。下面是传统IO服务器的示例图:

A classic IO server design - one connection handled by one thread.

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.06.04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java NIO和IO之间的主要区别
  • 面向流与面向缓冲区
  • 阻塞和非阻塞IO
  • 选择器(Selectors)
  • NIO和IO对应用设计的影响
    • API调用
      • 数据处理
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档