When studying both the Java NIO and IO API's, a question quickly pops into mind:
When should I use IO and when should I use NIO?
In this text I will try to shed some light on the differences between Java NIO and IO, their use cases, and how they affect the design of your code.
Main Differences of Java NIO and IO
The table below summarizes the main differences between Java NIO and IO. I will get into more detail about each difference in the sections following the table.
IO | NIO |
Stream oriented | Buffer oriented |
Blocking IO | Non blocking IO |
Selectors |
Stream Oriented vs. Buffer Oriented
The first big difference between Java NIO and IO is that IO is stream oriented, where NIO is buffer oriented. So, what does that mean?
Java IO being stream oriented means that you read one or more bytes at a time, from a stream. What you do with the read bytes is up to you. They are not cached anywhere. Furthermore, you cannot move forth and back in the data in a stream. If you need to move forth and back in the data read from a stream, you will need to cache it in a buffer first.
Java NIO's buffer oriented approach is slightly different. Data is read into a buffer from which it is later processed. You can move forth and back in the buffer as you need to. This gives you a bit more flexibility during processing. However, you also need to check if the buffer contains all the data you need in order to fully process it. And, you need to make sure that when reading more data into the buffer, you do not overwrite data in the buffer you have not yet processed.
Blocking vs. Non-blocking IO
Java IO's various streams are blocking. That means, that when a thread invokes a read()
or write()
, that thread is blocked until there is some data to read, or the data is fully written. The thread can do nothing else in the meantime.
Java NIO's non-blocking mode enables a thread to request reading data from a channel, and only get what is currently available, or nothing at all, if no data is currently available. Rather than remain blocked until data becomes available for reading, the thread can go on with something else.
The same is true for non-blocking writing. A thread can request that some data be written to a channel, but not wait for it to be fully written. The thread can then go on and do something else in the mean time.
What threads spend their idle time on when not blocked in IO calls, is usually performing IO on other channels in the meantime. That is, a single thread can now manage multiple channels of input and output.
Selectors
Java NIO's selectors allow a single thread to monitor multiple channels of input. You can register multiple channels with a selector, then use a single thread to "select" the channels that have input available for processing, or select the channels that are ready for writing. This selector mechanism makes it easy for a single thread to manage multiple channels.
How NIO and IO Influences Application Design
Whether you choose NIO or IO as your IO toolkit may impact the following aspects of your application design:
- The API calls to the NIO or IO classes.
- The processing of data.
- The number of thread used to process the data.
The API Calls
Of course the API calls when using NIO look different than when using IO. This is no surprise. Rather than just read the data byte for byte from e.g. an InputStream
, the data must first be read into a buffer, and then be processed from there.
The Processing of Data
The processing of the data is also affected when using a pure NIO design, vs. an IO design.
In an IO design you read the data byte for byte from an InputStream
or a Reader
. Imagine you were processing a stream of line based textual data. For instance:
Name: Anna Age: 25 Email: anna@mailserver.com Phone: 1234567890
This stream of text lines could be processed like this:
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();
Notice how the processing state is determined by how far the program has executed. In other words, once the firstreader.readLine()
method returns, you know for sure that a full line of text has been read. The readLine()
blocks until a full line is read, that's why. You also know that this line contains the name. Similarly, when the secondreadLine()
call returns, you know that this line contains the age etc.
As you can see, the program progresses only when there is new data to read, and for each step you know what that data is. Once the executing thread have progressed past reading a certain piece of data in the code, the thread is not going backwards in the data (mostly not). This principle is also illustrated in this diagram:
Java IO: Reading data from a blocking stream. |
A NIO implementation would look different. Here is a simplified example:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer);
Notice the second line which reads bytes from the channel into the ByteBuffer
. When that method call returns you don't know if all the data you need is inside the buffer. All you know is that the buffer contains some bytes. This makes processing somewhat harder.
Imagine if, after the first read(buffer)
call, that all what was read into the buffer was half a line. For instance, "Name: An". Can you process that data? Not really. You need to wait until at leas a full line of data has been into the buffer, before it makes sense to process any of the data at all.
So how do you know if the buffer contains enough data for it to make sense to be processed? Well, you don't. The only way to find out, is to look at the data in the buffer. The result is, that you may have to inspect the data in the buffer several times before you know if all the data is inthere. This is both inefficient, and can become messy in terms of program design. For instance:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { bytesRead = inChannel.read(buffer); }
The bufferFull()
method has to keep track of how much data is read into the buffer, and return either true
orfalse
, depending on whether the buffer is full. In other words, if the buffer is ready for processing, it is considered full.
The bufferFull()
method scans through the buffer, but must leave the buffer in the same state as before thebufferFull()
method was called. If not, the next data read into the buffer might not be read in at the correct location. This is not impossible, but it is yet another issue to watch out for.
If the buffer is full, it can be processed. If it is not full, you might be able to partially process whatever data is there, if that makes sense in your particular case. In many cases it doesn't.
The is-data-in-buffer-ready loop is illustrated in this diagram:
Java NIO: Reading data from a channel until all needed data is in buffer. |
Summary
NIO allows you to manage multiple channels (network connections or files) using only a single (or few) threads, but the cost is that parsing the data might be somewhat more complicated than when reading data from a blocking stream.
If you need to manage thousands of open connections simultanously, which each only send a little data, for instance a chat server, implementing the server in NIO is probably an advantage. Similarly, if you need to keep a lot of open connections to other computers, e.g. in a P2P network, using a single thread to manage all of your outbound connections might be an advantage. This one thread, multiple connections design is illustrated in this diagram:
Java NIO: A single thread managing multiple connections. |
If you have fewer connections with very high bandwidth, sending a lot of data at a time, perhaps a classic IO server implementation might be the best fit. This diagram illustrates a classic IO server design:
Java IO: A classic IO server design - one connection handled by one thread. |
相关推荐
在探讨Java.nio与Java.io之间的比较时,我们首先需要理解这两个包在Java编程语言中的核心作用和它们各自的优势。Java.io和Java.nio是Java中处理输入/输出操作的两个主要框架,它们各自拥有独特的特性和应用场景。 #...
java.io clojure.java.io 的 JK7 java.nio.file.Path 兼容性依赖信息该库托管在 Releases 上。 依赖: [me.moocar/java.io " 0.1.0 " ]用法是 JDK7 中引入的文件路径的抽象。 这个库提供了和 Paths 之间的兼容性。 ...
Java NIO(New IO)是Java平台提供的一种新的IO操作模式,它首次出现在Java 1.4版本中,并在后续版本中不断完善。Java NIO 的设计目的是为了克服传统Java IO API在面对大量并发连接时存在的性能瓶颈。 ##### 使用...
NIO(New IO)是Java平台中用于处理输入/输出操作的一种高级API,它在Java 1.4版本中引入,以替代传统的IO流模型。NIO提供了更高效、更灵活的数据传输方式,尤其适用于高并发和大数据量的场景。 在Java的NIO体系中...
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; public class FilesExample { public static void...
本资源“java_io.rar”提供了关于如何在Java中进行文件操作的示例代码,包括读取、移动、删除和复制文件等常见任务。我们将深入探讨这些主题,以便更好地理解Java I/O API的使用。 首先,让我们从读取本地文件开始...
### JAVANIO简介 #### 一、基本概念与传统IO 在计算机科学领域中,**IO(Input/Output)**是指主存与外部设备(如硬盘、终端、网络等)之间传输数据的过程。它是操作系统的一项核心功能,通过专用的I/O指令来实现。...
它在Java 1.4版本中被引入,替代了传统的IO模型,提供了更高级别的I/O操作机制,以适应并发编程的需求。NIO的核心在于通道(Channels)和缓冲区(Buffers)的概念,它们与传统的流(Streams)模型不同,允许进行异步...
涉及到java io, nio, aio相关知识点,学习过程中的一些总结,持续更新中,xmind 格式
Java NIO(New IO,也称为Non-blocking IO)和传统的Java IO是Java编程语言中用于处理I/O操作的两种主要技术。随着互联网用户数量的激增,企业对应用程序的并发处理能力提出了更高的要求。为了解决传统Java IO模型在...
JDK1.7 之 java.nio.file.Files 读取文件仅需一行代码实现 java.nio.file.Files 类是 JDK1.7 中引入的新的文件操作类,该类包含了许多有用的方法来操作文件。其中,Files.readAllBytes(Path) 方法可以将整个文件...
#### Java NIO vs. IO Java NIO 和传统的 Java IO 有着本质的区别: - **面向流与面向缓冲**:Java IO 面向流,意味着数据是按顺序读取的,并且不能前后移动;而 Java NIO 面向缓冲区,数据读取到缓冲区之后可以在...
not introduced with the other NIO types in Java 1.4 because they depend on the variable arguments capability that was introduced in Java 5.) NIO is missing several features, which were subsequently ...
Java NIO,全称为New Input/Output,是Java在1.4版本引入的一个新的I/O API,用于替代标准的Java IO API。这个新的API提供了一种不同于传统阻塞I/O模型的非阻塞I/O处理方式,使得在处理高并发、大数据传输场景时性能...
import java.io.File; import java.io.FileOutputStream; //import java.io.FileWriter; //import java.io.OutputStreamWriter; import java.io.PrintStream; //import java.nio.charset.Charset; //import ...
此外,Java NIO(New IO)在Java 1.4引入,是对传统IO的补充,提供了非阻塞IO、选择器和通道等高级特性。NIO的设计也遵循了类似的原则,虽然不是严格意义上的Decorator模式,但同样可以动态组合不同的通道和缓冲区,...