死磕Java之NIO与IO

向右看齐 2022-03-17 09:16 277阅读 0赞

死磕Java之NIO与IO

d3hfZm10PXBuZw

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

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

01

NIO与IO的区别

d3hfZm10PXBuZw

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




















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

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

02

面向流 VS 面向Buffer

d3hfZm10PXBuZw

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

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

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

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

03

阻塞与非租塞

d3hfZm10PXBuZw

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

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

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

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

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

04

Selector

d3hfZm10PXBuZw

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

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

05

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

d3hfZm10PXBuZw

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

1.NIO或IO类的API调用

2.数据处理

3.处理数据的线程量

API调用:

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

数据处理:

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

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

d3hfZm10PXBuZw

  1. Name: Anna
  2. Age: 25
  3. Email: anna@mailserver.com
  4. Phone: 1234567890

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

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

d3hfZm10PXBuZw

  1. InputStream input = ... ; // get the InputStream from the client socket
  2. BufferedReader reader = new BufferedReader(new InputStreamReader(input));
  3. String nameLine = reader.readLine();
  4. String ageLine = reader.readLine();
  5. String emailLine = reader.readLine();
  6. String phoneLine = reader.readLine();

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

值得注意的是处理状态如何取决于程序怎么执行到哪 一步。换句话说,一旦reader.readLine()调用返回, 你可知道的是一行数据已经被读取了。readLine()方法 被阻塞直到一整行被读取。

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

nio-vs-io-1.png

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

d3hfZm10PXBuZw

  1. ByteBuffer buffer = ByteBuffer.allocate(48);
  2. int bytesRead = inChannel.read(buffer);

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

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

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

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

  1. ByteBuffer buffer = ByteBuffer.allocate(48);
  2. int bytesRead = inChannel.read(buffer);
  3. while(! bufferFull(bytesRead) ) {
  4. bytesRead = inChannel.read(buffer);
  5. }

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

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

nio-vs-io-2.png

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

06

总结

d3hfZm10PXBuZw

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

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

nio-vs-io-3.png

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

nio-vs-io-4.png

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9wbmcvN1FSVHZrSzJxQzRiUHNLc2Zsa3lVSUdRQnR3YnJWT045em1DZG1XOUhuZnpkR21zNGVVSXpodzYwZXdtWTBySk5hZExZbFROZmRKVGhoSzBhZk4xdHcvMD93eF9mbXQ9cG5n

5c70ef86-01dc-40c4-a31b-7ab4ac10c663.png

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9naWYvdU4xTElhdjdvSmlieVN6VTRLVWRBSG1jWUpBUnRSUzlmQTY5Q2xGc3lzZzllNWlhc2gxNmFrYlpqdk1XdExRRkNvUldmeEpjRWdzdFJTT0NRaWN0eDJsckEvMD93eF9mbXQ9Z2lm

点击二维码,关注我们

aHR0cHM6Ly9tbWJpei5xbG9nby5jbi9tbWJpel9naWYvdU4xTElhdjdvSmlieVN6VTRLVWRBSG1jWUpBUnRSUzlmdUFZQmljNVJmTE5Ddm00WkhyY2tKaEJIdVFxUGV0MDRKdU56RjRma1BzUEswV1NRZEpUZ1JIQS8wP3d4X2ZtdD1naWY

15

发表评论

表情:
评论列表 (有 0 条评论,277人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java NIOIO

    当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异、它们的使

    相关 Java基础IONIO

    一、概念 NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在

    相关 Java泛型(一)

    死磕Java之泛型(一) ![d3hfZm10PXBuZw][] 一般的类和方法,只能使用具体的类型;要么是基本类型,要么是自定义的类,如果需要编写可以应用于多种类型的代码

    相关 Java NIOIO

    Java NIO和IO的主要区别 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异。 IO                NIO