APP下载

技术干货分享:Java NIO与IO的区别与应用(建议收藏)

消息来源:baojiabao.com 作者: 发布时间:2024-05-18

报价宝综合消息技术干货分享:Java NIO与IO的区别与应用(建议收藏)

在研究Java NIO和IO API时,很快就会发现一个问题:

我什么时候应该使用IO,什么时候应该使用NIO?

在本文中,我将尝试阐明Java NIO和IO之间的差异,它们的用例以及它们如何影响程式码的设计。

Java NIO和IO的主要区别

下表总结了Java NIO和IO之间的主要区别。我将在表格后面的部分中详细介绍每个区别。

IONIOStream orientedBuffer orientedBlocking IONon blocking IO

Selectors

Stream Oriented vs. Buffer Oriented

Java NIO和IO之间的第一个重要区别是IO是面向流的,其中NIO是面向缓冲区的。那么,这意味着什么?

面向流的Java IO意味着您可以从流中一次读取一个或多个字节。你对读取的字节做什么取决于你。它们不会快取在任何地方。此外,您无法在流中的资料中前后移动。如果需要在从流中读取的资料中前后移动,则需要先将其快取在缓冲区中。

Java NIO的面向缓冲区的方法略有不同。资料被读入缓冲区,稍后处理该缓冲区。你可以根据需要在缓冲区中前后移动。这使你在处理过程中具有更大的灵活性。但是,你还需要检查缓冲区是否包含完整处理所需的所有资料。并且,你需要确保在将更多资料读入缓冲区时,不要覆盖尚未处理的缓冲区中的资料。

Blocking vs. Non-blocking IO

Java IO的各种流都是blocking的。这意味着,当执行绪呼叫read()或write()时,该执行绪将被阻塞,直到有一些资料要读取,或者资料被完全写入,在此期间,该执行绪无法执行任何其他操作。

Java NIO的非阻塞模式允许执行绪请求从通道读取资料,并且只获取当前可用的内容,或者根本没有资料,如果当前没有资料可用。执行绪可以继续使用其他内容,而不是在资料可供读取之前保持阻塞状态。

非阻塞写入也是如此,执行绪可以请求将某些资料写入通道,但不要等待它完全写入。然后执行绪可以继续并在同一时间做其他事情。

执行绪在IO呼叫中没有阻塞时花费空闲时间,通常在此期间在其他通道上执行IO。也就是说,单个执行绪现在可以管理多个输入和输出通道。

Selectors

Java NIO的选择器允许单个执行绪监视多个输入通道。你可以使用选择器注册多个通道,然后使用单个执行绪“选择”具有可用于处理的输入的通道,或者选择准备写入的通道。这种选择器机制使单个执行绪可以轻松管理多个通道。

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

选择NIO或IO作为IO工具包可能会影响应用程序设计的以下方面:

API呼叫NIO或IO类。处理资料。用于处理资料的执行绪数。API呼叫

当然,使用NIO时的API呼叫看起来与使用IO时不同。这并不奇怪。而不是仅仅从例如InputStream读取字节的资料字节,必须首先将资料读入缓冲区,然后从那里进行处理。

资料处理

使用纯NIO设计与IO设计时,资料处理也会受到影响。

在IO设计中,您从InputStream或Reader中读取字节的资料字节。想象一下,您正在处理基于行的文字资料流。例如:

Name: Anna

Age: 25

Email: [email protected]

Phone: 1234567890

这个文字行流可以像这样处理:

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()会阻塞直到读取整行,这就是原因。您还知道此行包含名称。同样,当第二个readLine()呼叫返回时,您知道此行包含年龄等。

正如您所看到的,只有当有新资料要读取时,程式才会进行,并且对于每个步骤,您都知道该资料是什么。一旦执行的执行绪已经超过读取程式码中的某个资料片段,该执行绪就不会在资料中向后移动(通常不会)。此图中还说明了此原则:

Java IO:从阻塞流中读取资料。

NIO的实现看起来会有所不同。这是一个简化的例子:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

注意第二行从通道读取字节到ByteBuffer。当该方法呼叫返回时,您不知道所需的所有资料是否都在缓冲区内。你只知道缓冲区包含一些字节,这使得处理更加困难。

想象一下,在第一次读取(缓冲)呼叫之后,是否所有读入缓冲区的内容都是半行。例如,“姓名:An”。你能处理这些资料吗?并不是的。在完成任何资料的处理之前,您需要等待至少一整行资料进入缓冲区。

那么你怎么知道缓冲区是否包含足够的资料来处理它?好吧,你没有。找出的唯一方法是检视缓冲区中的资料。结果是,在您知道所有资料是否存在之前,您可能需要多次检查缓冲区中的资料。这既低效又可能在程式设计方面变得混乱。例如:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

while(! bufferFull(bytesRead) ) {

bytesRead = inChannel.read(buffer);

}

bufferFull()方法必须跟踪读入缓冲区的资料量,并返回true或false,具体取决于缓冲区是否已满。换句话说,如果缓冲区已准备好进行处理,则认为它已满。

bufferFull()方法扫描缓冲区,但必须使缓冲区保持与呼叫bufferFull()方法之前相同的状态。如果不是,则可能无法在正确的位置读入读入缓冲区的下一个资料。这不是不可能的,但这是另一个需要注意的问题。

如果缓冲区已满,则可以对其进行处理。如果它不满,您可能能够部分处理那里的任何资料,如果这在您的特定情况下是有意义的。在许多情况下,它没有。

这个图中说明了is-data-in-buffer-ready循环:

Java NIO:从通道读取资料,直到所有需要的资料都在缓冲区中。

摘要

NIO允许您仅使用一个(或几个)执行绪来管理多个通道(网络连线或档案),但成本是解析资料可能比从阻塞流中读取资料时更复杂。

如果您需要同时管理数千个开启的连线,每个只发送一些资料,例如聊天服务器,在NIO中实现服务器可能是一个优势。同样,如果您需要与其他计算机保持大量开放连线,例如在P2P网络中,使用单个执行绪来管理所有出站连线可能是一个优势。此图中说明了这一个执行绪,多个连线设计:

Java NIO:管理多个连线的单个执行绪。

如果您拥有较少带宽的连线,一次传送大量资料,那么可能最经典的IO服务器实现可能是最合适的。此图说明了经典的IO服务器设计:

Java IO:经典的IO服务器设计 - 由一个执行绪处理的一个连线。

简化理解

就读取速度来说:CPU > 内存 > 硬盘

I- 就是从硬盘到内存

O- 就是从内存到硬盘

第一种方式:从硬盘读取资料,然后程式一直等,资料读完后,继续你的操作。这种方式是最简单的,叫阻塞IO。

第二种方式:从硬盘读取资料,然后程式继续向下执行,等资料读取完后,通知当前程式读取完成(对硬件来说叫中断,对程式来说叫回调),然后此程式可以立即处理读取的资料,也可以执行完当前操作后再对读取完的资料进行操作。

总结

操作系统是按块 Block从硬盘拿资料,就如同一个大脸盆,一下子就放入了一盆水。但是,当 Java 使用的时候,旧的 IO 确实基于 流 Stream的,也就是虽然操作系统给我了一脸盆水,但是我得用吸管慢慢喝。

于是,NIO 横空出世。。。

end:如果你觉得本文对你有帮助的话,记得点赞转发,你的支援就是我更新动力。

2019-11-15 01:50:00

相关文章