Java NIO与IO

在研究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。也就是说,单个线程现在可以管理多个输入和输出通道。

选择

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

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

无论你选择NIO还是IO,因为你的IO工具包可能会影响应用程序设计的以下方面:

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

API调用

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

数据处理

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

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

姓名:qq
年龄:7岁
电子邮件:qq@mail.com
电话:1234567890

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

InputStream input = ...; //从客户端套接字获取InputStream

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()调用返回时,你知道此行包含年龄等。

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

在这里插入图片描述

|

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服务器设计 - 由一个线程处理的一个连接。

推荐阅读更多精彩内容

  • Java NIO系列教程(十二) Java NIO与IO | 并发编程网 – ifeve.com http://...
    葡萄喃喃呓语阅读 491评论 0 5
  • 原文地址:http://tutorials.jenkov.com/java-nio/nio-vs-io.html作...
    青川刺客阅读 313评论 0 1
  • 概述 NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区),Selector。 传统IO基于...
    时之令阅读 3,111评论 0 8
  • Java NIO与IO的区别和比较 J2SE1.4以上版本中发布了全新的I/O类库。本文将通过一些实例来简单介绍N...
    cs_yale阅读 3,035评论 2 7
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 6,663评论 1 142
  • 最近全国倒春寒。宝宝易感冒。中医讲,人的身体是一个坚固的堡垒,正气足,不易感冒,正气是从哪儿来的呢?无论大人...
    古城深巷01阅读 125评论 0 0
  • 相隔千里 杳无音信的过去几十年 你是不是已将我遗忘…… 可你的音容 却常常入我的梦乡 难道在“巴彦克拉相遇后 只能...
    杨柳依依简阅读 181评论 3 6
  • 想催生良好的欲望,自我激励是一个有效的方法。而最有激励性的办法,莫过于“临终考验”。即假设自己快去世了,在这个时候...
    目童_f97b阅读 105评论 0 2
  • 《习惯的力量》思维导图 《习惯的力量》告诉我们习惯它背后的原理到底是什么,我们怎么样才能够应用习惯来改变我们的生活...
    何以立群阅读 227评论 0 2