Java NIO系列教程(十二) Java NIO与IO
原文地址:http://tutorials.jenkov.com/java-nio/nio-vs-io.html
作者:Jakob Jenkov 译者:郭蕾 校对:方腾飞
当学习了Java NIO和IO的API后,一个问题马上涌入脑海:
我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异、它们的使用场景,以及它们如何影响您的代码设计。
Java NIO和IO的主要区别
下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异。
IO NIO 面向流 面向缓冲 阻塞IO 非阻塞IO 无 选择器
面向流与面向缓冲
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
选择器(Selectors)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
NIO和IO如何影响应用程序的设计
无论您选择IO或NIO工具箱,可能会影响您应用程序设计的以下几个方面:
- 对NIO或IO类的API调用。
- 数据处理。
- 用来处理数据的线程数。
API调用
当然,使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外,因为并不是仅从一个InputStream逐字节读取,而是数据必须先读入缓冲区再处理。
数据处理
使用纯粹的NIO设计相较IO设计,数据处理也受到影响。
在IO设计中,我们从InputStream或 Reader逐字节读取数据。假设你正在处理一基于行的文本数据流,例如:
Name: Anna Age: 25 Email: anna@mailserver.com Phone: 1234567890
该文本行的流可以这样处理:
InputStream input = … ; // get the InputStream from the client socket
[code lang=”java”]
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();
[/code]
请注意处理状态由程序执行多久决定。换句话说,一旦reader.readLine()方法返回,你就知道肯定文本行就已读完, readline()阻塞直到整行读完,这就是原因。你也知道此行包含名称;同样,第二个readline()调用返回的时候,你知道这行包含年龄等。 正如你可以看到,该处理程序仅在有新数据读入时运行,并知道每步的数据是什么。一旦正在运行的线程已处理过读入的某些数据,该线程不会再回退数据(大多如此)。下图也说明了这条原则: (Java IO: 从一个阻塞的流中读数据) 而一个NIO的实现会有所不同,下面是一个简单的例子:
[code lang=”java”]
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
[/code]
注意第二行,从通道读取字节到ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。
假设第一次 read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。
所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了,你不知道。发现的方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据。这不仅效率低下,而且可以使程序设计方案杂乱不堪。例如:
[code lang=”java”]
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}
[/code]
bufferFull()方法必须跟踪有多少数据读入缓冲区,并返回真或假,这取决于缓冲区是否已满。换句话说,如果缓冲区准备好被处理,那么表示缓冲区满了。
bufferFull()方法扫描缓冲区,但必须保持在bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的,但却是需要注意的又一问题。
如果缓冲区已满,它可以被处理。如果它不满,并且在你的实际案例中有意义,你或许能处理其中的部分数据。但是许多情况下并非如此。下图展示了“缓冲区数据循环就绪”:
Java NIO:从一个通道里读数据,直到所有的数据都读到缓冲区里.
3) 用来处理数据的线程数
NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。一个线程多个连接的设计方案如下图所示:
Java NIO: 单线程管理多个连接
如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。下图说明了一个典型的IO服务器设计:
Java IO: 一个典型的IO服务器设计- 一个连接通过一个线程处理.
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Java NIO系列教程(十二) Java NIO与IO
赞一个 写得不错!
下面的两个图有什么区别
同问
图画的不是太好 ,上面的图是说 多个connection 对应一个thread ,下面的图是说 每个connection 对应一个thread , 根据我的理解图这么画会好点、
|——————|
| Thread |
|————-| |——————|
| Thread |———————->| |————| |
|————-| | | Connection | |
|ServerSocket | | |————| |
|————-| | | Connection | |
| |————| |
|——————|
====================================================================
|——————|
| Thread |
|————-| |——————|
| Thread |———————->| Connection |
|————-| |——————|
|ServerSocket |
|————-|———————->|——————|
| Thread |
|——————|
| Connection |
|——————|
坑啊, 好不容易画好的 发表之后就乱了。
截图吧
图画的不是太好 ,上面的图是说 多个connection 对应一个thread ,下面的图是说 每个connection 对应一个thread , 根据我的理解图这么画会好点、
|==================|
| Thread |
|=============| |==================|
| Thread |———————->| |============| |
|=============| | | Connection | |
|ServerSocket | | |============| |
|=============| | | Connection | |
| |============| |
|==================|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|==================|
| Thread |
|=============| |==================|
| Thread |======================>| Connection |
|=============| |==================|
|ServerSocket |
|=============|======================>|==================|
| Thread |
|==================|
| Connection |
|==================|
hi, 读完了关于 nio 的文章。 请问有没有相关的全面点的 demo 能更深刻的理解?
is-data-in-buffer-ready loop 翻译成“缓冲区数据循环就绪”,合适吗?
不会啊 、
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。
没有数据可读之前做别的事情,这个别的事情泛指哪些事情,假如我这个线程就需要读出的所有数据,然后进行打印,那这时候打印出来的是部分数据,还是所有数据。
你可以写个回调或者Future来把数据读取完之后的处理代码放进去,然后可以不等待数据处理完就可以做任何事情,比如继续监听客户端请求之类的。 但总感觉NIO是以增加了数据处理复杂度来换取cpu的时间。
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}
一定要这样不断的轮训检查吗,这样效率貌似和阻塞io没有啥区别啊。而且什么时候判断数据是完整的呢?
int bytesRead = inChannel.read(buffer); 这里返回的字节数是-1的时候证明你已经读完了,前几篇文章有写。至于有没有读取到完整数据需要你自己的代码控制好。第三章通道的示例里面有写:http://ifeve.com/channels/
为什么NIO是同步非阻塞的? 同步是哪里同步?阻塞是哪里阻塞?
NIO的应用场景:多连接小数据。你可以这样认为,线程处理完某一连接的一部分数据之后,下一部分数据还没到,这样,线程有时间先去处理另一个连接的数据。楼主的例子中没有体现多连接的情况。
NIO的同步应该是说的NIO的底层实现,有同步非阻塞和异步非阻塞两种实现方式,参见:http://www.artima.com/articles/io_design_patterns.html
读完整个系列,对Nio有了个大概的了解,十分感谢!
读完整个系列,对NIO有大致的了解了,非常感谢!但是,请问,有没有一个比较完善点的例子,能体现NIO在实际工作中的运用吗?目前,对NIO的优势持疑惑状态,不能体会它的优点,望解答~
可以对比使用下Java IO ,使用起来NIO比较方便,有更多更高级的API,比如transferTo三行代码就能实现从一个文件读取数据到另外一个文件。
nio既然是异步的, 那有没有回调函数或者defer函数呢?
bufferFull()方法扫描缓冲区,但必须保持在bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的,但却是需要注意的又一问题。
这一段翻译有问题,应该搞成:
通过bufferFull()方法扫描缓冲区,缓冲区必须保持和bufferFull()方法被调用之前相同的状态,如果不是,下一个读入缓冲区的数据可能无法被读到正确的位置,这并非不可能,但也暴露出另一个需要我们注意的问题
“这是不可能的,但却是需要注意的又一问题。” 应该是“这不是不可能的,但却是需要注意的又一问题。”原文This is not impossible, but it is yet another issue to watch out for.
nio处理多而小的连接,用了缓冲区,那么会不会时时性不好了呢?
图片都坏了,看不到
点进原文链接可以看到
好文章,但文中图片都不能显示,楼主可不可以补一下?
bufferFull
怎么没有发现这个方法
话说仔细看了这一些列12期的nio文章,还是不知道具体怎么用,有没有深点的应用型的文章
mark!
图加载不了
这些图怎么都不显示啦?
楼主,您好,里面的图全都看不到
看不到图
楼主,哪里有图啊,没有图啊,原文中倒是有图
“bufferFull()方法扫描缓冲区,但必须保持在bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的,但却是需要注意的又一问题。”
这里是说保持bufferFull()调用前后的缓冲区状态(position/limit)是不可能的吗? 可以通过检查前 mark position的位置,在检查完成通过reset来保证检查前后缓冲区的状态一致。
QAQ~学习学习
这个翻译,,有点强行了
图没有了大佬