死磕Java之NIO与IO

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Oeljeklaus/article/details/87892610

死磕Java之NIO与IO

    当学习Java NIO与IO时,你是否会有这样的想法:什么时候使用NIO,什么使用IO呢?本篇文章将会分析两者的不同,它们的用例,以及和影响代码的设计。

01

NIO与IO的区别

    下面这张表总结了Java NIO和IO的主要区别,接下来我将从下表中的不同更加细致的讲解。

IO NIO
Stream oriented Buffer oriented
Blocking IO Non blocking IO
  Selectors

02

面向流 VS 面向Buffer

    Java NIO与传统IO最大的不同是NIO为面向Buffer的,传统的IO是面向流的。

     Java IO面向流意味着每次你都可以从流中读取一个或者多个字节。从流中读取多少字节完全是由程序员决定的。这些字节并不能够在任何地方缓存;这就意味着你不能从流中前后移动数据。如果真的需要移动数据,那么需要将首先将这些数据缓存在缓冲区中。

    Java NIO面向Buffer稍有不同。数据被读取到buffer中,稍后将会buffer处理。只要你需要,你可以在buffer中移动数据;这给予了程序员处理时更多的灵活性。然而,你必须检查buffer中是否包含你需要处理的所有数据。并且,你需要确保当读取数据到buffer中时,你不能覆盖掉没有处理的数据。

03

阻塞与非租塞

    Java IO中各种各样的流都是阻塞的,这就意味着当调用read()方法和write()方法时,线程将会阻塞直到数据被读取或者被完全的写入。在此期间,处理流的线程将不能做任何事情。

    Java NIO非阻塞模式确保线程要求从channel中读取数据,仅仅直到此时channel是可用的,或者如果此时没有数据可用,就啥也不做。而不是保存阻塞直到数据可以被读取,这里线程可以可以做其他事情。

    相似地,线程要求数据能被写入到channel当数据能被完全写入。这期间,线程可以做其他事情。

    线程耗费其他时间在其他未阻塞的IO调用中,这期间可以处理其他channel的 IO上,这就意味着一个线程可以管理多个输入channel和输出channel。

04

Selector

    Java NIO的Selectors允许单线程监视多个输入channel。你能将多个channel注册到selector中,使用单个线程去"选择"可以读数据和写数据的channel。Selector可以使单线程更加易于管理多个channel。

05

NIO和IO如何影响应用程序设计

    无论你选择NIO或者IO,你的IO处理程序将会从下面几个方法影响应用程序的设计:

        1.NIO或IO类的API调用

        2.数据处理

        3.处理数据的线程量

    API调用:

        使用NIO和IO的API确实调用不同。数据必须首先被     读到buffer中,然后将会被处理而不是从例如InputStr     eam中一个字    节一个字节的读取。

    数据处理:

        当使用纯粹的Java NIO或Java IO,数据处理也被       影响。

        在传统IO设计中你必须从InputStream或者Reade     r一个字节一个字节的读取。假设你处理下面的文本:

    

Name: Anna
Age: 25
Email: [email protected]
Phone: 1234567890

        一般的IO处理程序可能如下:

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()方法    被阻塞直到一整行被读取。

        正如你说见到的一样,仅仅当新的数据需要读取,每    一步你都知道数据内容。一旦执行线程处理代码的确定    数据,线程并不能倒退数据。下图表述了上述原则:

   nio-vs-io-1.png

        NIO实现将看起来不同,这里有个简单例子:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

        我们看到代码的第二行,将数据从Channel读到Byt    eBuffer。你不能确定在buffer内部的数据是否是你需    要的,如果上述函数调用返回。你所知道的是,buffer    里含有多少字节。这使得处理有一点困难。

        想象一下,当第一次读取之后,被读入到buffer的    数据有可能是半行数据。举个例子,读取的数据是"Na    me:An",你能处理数据吗?显然是不能的,你需要等到    一行的数据被读入buffer中,这才使处理数据变得有意    义。

        因此那么怎么知道buffer含有的数据被你处理是有    意义呢?显然,你并不知道。唯一的方式是看数据是否    被读到buffer中。结果是,你可能需要多次检查buffer    中的数据在你知道所有数据存在之前。从设计角度来        看,这显得低效率和混乱。例如:

    

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

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

        bufferFull()方法将会记录有多少数据被读入到buf    fer中,该方法返回true或者false取决于buffer是否被    填充满。换句话说,如果buffer被处理,那么buffer被    填满了。

        下面是buffer被数据填充循环如下:

nio-vs-io-2.png

06

总结

    NIO允许你管理多个channel使用单个线程,但是解析数据的开销可能有一点复杂当从阻塞流读取数据时。

    如果你需要同时管理上千开放连接,当每一个连接发送一点数据,例如一个聊天服务器,使用NIO实现可能比较有优势。相似地,如果你要保存和其他电脑的连接,例如P2P网络,使用单线程去管理你的对外连接是优势。一个线程处理多个连接可以被描述为下图:

nio-vs-io-3.png

    如果有很少的连接并且要求有很大的带宽,在一个时刻发送大量数据,传统的IO实现可能更适合。经典的IO实现描述如下图:

nio-vs-io-4.png

点击二维码,关注我们

15

猜你喜欢

转载自blog.csdn.net/Oeljeklaus/article/details/87892610