1. Introduction
1.绪论
In this tutorial, we’ll cover Java IO functionalities and how they changed throughout different Java versions. First, we’ll cover the java.io package from the initial Java version. Next, we’ll go over java.nio package introduced in Java 1.4. In the end, we’ll cover the java.nio.file package, commonly known as the NIO.2 package.
在本教程中,我们将介绍Java IO的功能,以及它们在不同Java版本中的变化。首先,我们将介绍最初的Java版本中的java.io包。接下来,我们将介绍Java 1.4中引入的java.nio包。最后,我们将介绍java.nio.file包,通常称为NIO.2包。
2. Java NIO Package
2.Java NIO包
The first Java version was released with the java.io package, introducing a File class to access the file system. The File class represents files and directories and provides limited operations on the file system. It was possible to create and delete files, check if they exist, check read/write access, etc.
第一个Java版本是随着java.io包发布的,引入了File类来访问文件系统。文件类代表文件和目录,并提供了对文件系统的有限操作。可以创建和删除文件,检查它们是否存在,检查读/写访问,等等。
It also has some shortcomings:
它也有一些不足之处。
- Lack of copy method – to copy a file, we need to create two File instances and use a buffer to read from one and write to another File instance.
- Bad error handling – some methods return boolean as an indicator if an operation is successful or not.
- A limited set of file attributes – name, path, read/write privileges, memory size is available, to name a few.
- Blocking API – our thread is blocked until the IO operation is complete.
To read a file, we need a FileInputStream instance to read bytes from the file:
要读取一个文件,我们需要一个FileInputStream实例来读取文件中的字节。
@Test
public void readFromFileUsingFileIO() throws Exception {
File file = new File("src/test/resources/nio-vs-nio2.txt");
FileInputStream in = new FileInputStream(file);
StringBuilder content = new StringBuilder();
int data = in.read();
while (data != -1) {
content.append((char) data);
data = in.read();
}
in.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
Next, Java 1.4 introduces non-blocking IO API bundled in java.nio package (nio stands for new IO). NIO was introduced to overcome the limitations of the java.io package. This package introduced three core classes: Channel, Buffer, and Selector.
接下来,Java 1.4引入了捆绑在java.nio包中的非阻塞IO API(nio代表新IO)。NIO的引入是为了克服java.io包的限制。这个包引入了三个核心类。Channel, Buffer, 和Selector。
2.1. Channel
2.1 通道
Java NIO Channel is a class that allows us to read and write to a buffer. Channel class is similar to Streams (here we speak of IO Streams, not Java 1.8 Streams) with a couple of differences. Channel is a two-way street while Streams are usually one-way, and they can read and write asynchronously.
Java NIO Channel是一个允许我们向缓冲区读写的类。 Channel类类似于Streams(这里我们说的是IOStreams,而不是Java 1. Channel是双向的,而Streams通常是单向的,而且它们可以异步读写。
There are couple implementations of the Channel class, including FileChannel for file system read/write, DatagramChannel for read/write over a network using UDP, and SocketChannel for read/write over a network using TCP.
Channel类有几个实现,包括用于文件系统读写的FileChannel、DatagramChannel 用于使用UDP在网络上读写,以及SocketChannel用于使用TCP在网络上读写。
2.2. Buffer
2.2.缓冲区
Buffer is a block of memory from which we can read or write data into it. NIO Buffer object wraps a memory block. Buffer class provides a set of functionalities to work with the memory block. To work with Buffer objects, we need to understand three major properties of the Buffer class: capacity, position, and limit.
缓冲区是一个内存块,我们可以从中读取或写入数据。NIO的Buffer对象包装了一个内存块。Buffer类提供了一系列的功能来处理这个内存块。为了使用Buffer对象,我们需要了解Buffer类的三个主要属性:容量、位置和限制。
- Capacity defines the size of the memory block. When we write data to the buffer, we can write only a limited length. When the buffer is full, we need to read the data or clear it.
- The position is the starting point where we write our data. An empty buffer starts from 0 and goes to capacity – 1. Also, when we read the data, we start from the position value.
- Limit means how we can write and read from the buffer.
There are multiple variations of the Buffer class. One for each primitive Java type, excluding the Boolean type plus the MappedByteBuffer.
Buffer类有多种变化。每个原始的Java类型都有一个,不包括Boolean类型和MappedByteBuffer。
To work with a buffer, we need to know a few important methods:
要使用缓冲区,我们需要知道几个重要的方法。
- allocate(int value) – we use this method to create a buffer of a certain size.
- flip() – this method is used to switch from write to read mode
- clear() – method for clearing the content of the buffer
- compact() – method for clearing only the content we have already read
- rewind() – resets position back to 0 so we can reread the data in the buffer
Using previously described concepts, let’s use Channel and Buffer classes to read content from file:
使用之前描述的概念,让我们使用Channel和Buffer类来读取文件的内容。
@Test
public void readFromFileUsingFileChannel() throws Exception {
RandomAccessFile file = new RandomAccessFile("src/test/resources/nio-vs-nio2.txt", "r");
FileChannel channel = file.getChannel();
StringBuilder content = new StringBuilder();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
content.append((char) buffer.get());
}
buffer.clear();
bytesRead = channel.read(buffer);
}
file.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
After initializing all required objects, we read from the channel into the buffer. Next, in the while loop, we mark the buffer for reading using the flip() method and read one byte at a time, and append it to our result. In the end, we clear the data and read another batch.
在初始化所有需要的对象后,我们从通道中读入缓冲区。接下来,在while循环中,我们使用flip()方法标记缓冲区进行读取,每次读取一个字节,并将其追加到我们的结果。最后,我们清空数据并读取另一批数据。
2.3. Selector
2.3. 选择器
Java NIO Selector allows us to manage multiple channels with a single thread. To use a selector object to monitor multiple channels, each channel instance must be in the non-blocking mode, and we must register it. After channel registration, we get a SelectionKey object representing the connection between channel and selector. When we have multiple channels connected to a selector, we can use the select() method to check how many channels are ready for use. After calling the select() method, we can use selectedKeys() method to fetch all ready channels.
Java NIO Selector允许我们用一个线程管理多个通道。要使用选择器对象来监控多个通道,每个通道实例必须处于非阻塞模式,而且我们必须注册它。在通道注册后,我们得到一个SelectionKey对象,代表通道和选择器之间的连接。当我们有多个通道连接到一个选择器时,我们可以使用select()方法来检查有多少通道可以使用。在调用select()方法后,我们可以使用selectedKeys()方法来获取所有准备好的通道。
2.4. Shortcomings of NIO Package
2.4.NIO包的缺点
The changes java.nio package introduced is more related to low-level data IO. While they allowed non-blocking API, other aspects remained problematic:
java.nio包引入的变化更多的是与低级数据IO有关。虽然他们允许非阻塞的API,但其他方面仍然存在问题。
- Limited support for symbolic links
- Limited support for file attributes access
- Missing better file system management tools
3. Java NIO.2 Package
3.Java NIO.2包
Java 1.7 introduces new java.nio.file package, also known as NIO.2 package. This package follows an asynchronous approach to non-blocking IO not supported in java.nio package. The most significant changes are related to high-level file manipulation. They are added with Files, Path, and Paths classes. The most notable low-level change is the addition of AsynchroniousFileChannel and AsyncroniousSocketChannel.
Java 1.7引入了新的java.nio.file包,也被称为NIO.2包。该包采用了java.nio包中不支持的非阻塞性IO的异步方法。最重要的变化是与高层文件操作有关。它们被加入了Files, Path, 和Paths类。最显著的低层变化是增加了AsynchroniousFileChannel和AsynchroniousSocketChannel。
Path object represents a hierarchical sequence of directories and file names separated by a delimiter. The root component is furthest to the left, while the file is right. This class provides utility methods such as getFileName(), getParent(), etc. The Path class also provides resolve and relativize methods that help construct paths between different files. Paths class is a set of static utility methods that receive String or URI to create Path instances.
Path对象表示一个由分隔符分隔的目录和文件名的层次序列。根组件在最左边,而文件在右边。这个类提供了诸如getFileName()、getParent()等实用方法。Path类还提供了resolve和relativize方法,帮助构建不同文件之间的路径。路径类是一组静态实用方法,接收String或URI以创建Path实例。
Files class provides utility methods that use the previously described Path class and operate on files, directories, and symbolic links. It also provides a way to read many file attributes using readAttributes() method.
Files类提供了实用的方法,这些方法使用之前描述的Path类并对文件、目录和符号链接进行操作。它还提供了一种使用readAttributes()方法来读取许多文件属性的方法。
In the end, let’s see how NIO.2 compares to previous IO versions when it comes to reading a file:
最后,让我们看看NIO.2在读取文件时与以前的IO版本相比如何。
@Test
public void readFromFileUsingNIO2() throws Exception {
List<String> strings = Files.readAllLines(Paths.get("src/test/resources/nio-vs-nio2.txt"));
assertThat(strings.get(0)).isEqualTo("Hello from file!");
}
4. Conclusion
4.总结
In this article, we covered the basics of java.nio and java.nio.file packages. As we can see, NIO.2 is not the new version of the NIO package. The NIO package introduced a low-level API for non-blocking IO, while NIO.2 introduced better file management. These two packages are not synonymous, rather a compliment to each other. As always, all code samples can be found over on GitHub.
在这篇文章中,我们介绍了java.nio和java.nio.file包的基本知识。我们可以看到,NIO.2并不是NIO包的新版本。NIO包为非阻塞性IO引入了一个低级别的API,而NIO.2则引入了更好的文件管理。这两个包不是同义词,而是相互补充的。一如既往,所有的代码样本都可以在GitHub上找到over。