Java NIO和IO的区别

本文对比了Java NIO和IO的主要区别,包括面向缓冲与面向流、阻塞与非阻塞IO、选择器的使用等方面。并探讨了这些差异如何影响应用程序的设计,特别是API调用、数据处理方式及所需的线程数量。

下表总结了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工具箱,可能会影响您应用程序设计的以下几个方面:

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

API调用

当然,使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外,因为并不是仅从一个InputStream逐字节读取,而是数据必须先读入缓冲区再处理。

数据处理

使用纯粹的NIO设计相较IO设计,数据处理也受到影响。

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

 

复制代码代码如下:


Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890

 

该文本行的流可以这样处理:

复制代码代码如下:


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);

 

注意第二行,从通道读取字节到字节缓冲区。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。
假设第一次read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。

所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了,你不知道。发现的方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据这不仅效率低下,而且可以使程序设计方案杂乱不堪例如。:

代码复制代码如下:


ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

while(!bufferFull(bytesRead)){

bytesRead = inChannel.read(buffer);

}

 

bufferFull()方法必须跟踪有多少数据读入缓冲区,并返回真或假,这取决于缓冲区是否已满。换句话说,如果缓冲区准备好被处理,那么表示缓冲区满了。

bufferFull()方法扫描缓冲区,但必须保持在bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的,但却是需要注意的又一问题。

如果缓冲区已满,它可以被处理。如果它不满,并且在你的实际案例中有意义,你或许能处理其中的部分数据。但是许多情况下并非如此。下图展示了“缓冲区数据循环就绪“:3)用来


处理数据的线程数

NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。

如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。一个线程多个连接的设计方案如



Java NIO:单线程管理多个连接


如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合下图说明了一个典型的IO服务器设计:


Java IO:一个典型的IO服务器设计 - 一个连接通过一个线程处理

### 三层结构详解:Java NIOIO区别 #### 面向流与面向缓冲 Java IO 是面向流的,意味着每次从流中读取一个或多个字节,无法在数据流中前后移动。而 Java NIO 是面向缓冲的,数据首先被读取到缓冲区中,之后可以在缓冲区中灵活地操作数据。这种机制使得 NIO 在处理大数据量时效率更高,因为可以减少系统调用上下文切换的次数[^1]。 #### 阻塞与非阻塞 IO Java IO 的操作是阻塞的,即当调用 `read()` 或 `write()` 方法时,线程会一直阻塞,直到数据读取或写入完成。而 Java NIO 支持非阻塞模式,线程可以在没有数据就绪的情况下继续执行其他任务,从而提高并发性能。这种特性使得 NIO 更适合高并发的网络编程场景[^1]。 #### 选择器机制 Java NIO 引入了选择器(Selector)机制,它允许单个线程管理多个通道(Channel)。通过选择器,可以监听多个通道的事件(如连接、读就绪等),并在事件触发时进行相应的处理。这种机制显著减少了线程的数量,降低了线程管理上下文切换的开销,适用于大规模并发连接的场景[^1]。 #### 示例代码:NIO 文件操作 以下是一个使用 Java NIO 进行文件操作的示例代码,展示了如何通过 `FileChannel` `ByteBuffer` 实现高效的文件读写操作。 ```java import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FileChannelExample { public static void main(String[] args) { try (RandomAccessFile file = new RandomAccessFile("example.txt", "rw"); FileChannel fileChannel = file.getChannel()) { // 创建Buffer并写入数据 ByteBuffer buffer = ByteBuffer.allocate(1024); String data = "Hello, FileChannel!"; buffer.put(data.getBytes()); // 准备Buffer进行读操作 buffer.flip(); fileChannel.write(buffer); // 清空Buffer以便再次写入 buffer.clear(); // 设置Channel位置并读取数据 fileChannel.position(0); int bytesRead = fileChannel.read(buffer); System.out.println("读取 " + bytesRead + " 字节"); // 准备Buffer进行读操作 buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } } catch (Exception e) { e.printStackTrace(); } } } ``` #### 性能与适用场景 Java NIO 的设计初衷是为了提升 IO 操作的性能,尤其是在处理大量并发连接时。由于 NIO 提供了缓冲区非阻塞机制,它更适合需要高性能高并发的应用场景,如网络服务器客户端。而传统的 Java IO 更适合简单的文件操作少量连接的场景,因为其编程模型更为直观简单[^2]。 #### 扩展框架:Netty Netty 是一个基于 Java NIO 的高性能网络编程框架,广泛用于构建高性能的网络服务器客户端。Netty 提供了丰富的功能,如异步事件驱动的网络应用支持,简化了 NIO 编程的复杂性,并提高了开发效率应用性能。通过 Netty,开发者可以更容易地利用 NIO 的优势,构建稳定、高效的网络应用。 #### 相关问题 1. Java NIO 中的缓冲区是如何工作的? 2. 如何在实际项目中使用 Netty 框架? 3. Java IO NIO 在网络编程中的具体应用场景有哪些? 4. 非阻塞 IO 在高并发场景下的优势体现在哪些方面? 5. 如何选择适合的 IO 模型来优化应用程序性能?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值