早些时候,我们了解了IO API(输入/输出应用程序编程接口)和java.io包,它们的类主要用于处理 Java 中的流。这里的关键是流的概念。
今天我们将开始考虑NIO API(新输入/输出)。
这两种 I/O 方法的主要区别在于 IO API 是面向流的,而 NIO API 是面向缓冲区的。所以要理解的主要概念是buffers和channels。
什么是缓冲区,什么是通道?
通道是数据进出的逻辑入口,而缓冲区是此传输数据的源或目的地。在输出过程中,将要发送的数据放入缓冲区,缓冲区将数据传递给通道。在输入期间,来自通道的数据被放入缓冲区。
换句话说:
- 缓冲区只是一块内存,我们可以在其中写入信息,也可以从中读取信息,
- 通道是提供对 I/O 设备(例如文件或套接字)的访问的网关。
通道与 java.io 包中的流非常相似。所有去往任何地方(或来自任何地方)的数据都必须通过通道对象。通常,要使用 NIO 系统,您会获得一个到 I/O 实体的通道和一个用于存储数据的缓冲区。然后您使用缓冲区,根据需要输入或输出数据。
您可以在缓冲区中前后移动,即“遍历”缓冲区,这是您在流中无法做到的事情。这在处理数据时提供了更大的灵活性。在标准库中,缓冲区由抽象Buffer类及其几个后代表示:
- 字节缓冲区
- 字符缓冲区
- 短缓冲区
- 缓冲区
- 浮动缓冲区
- 双缓冲
- 长缓冲区
子类之间的主要区别在于它们存储的数据类型——字节、整数、长整型和其他原始数据类型。
缓冲区属性
缓冲区有四个主要属性。这些是容量、限制、位置和标记。
容量是可以存储在缓冲区中的最大数据量/字节数。缓冲区的容量无法更改。一旦缓冲区已满,必须先清除缓冲区,然后再向其写入更多内容。
在写入模式下,缓冲区的限制与其容量相同,表示可以写入缓冲区的最大数据量。在读取模式下,缓冲区的限制是指可以从缓冲区读取的最大数据量。
position指示光标在缓冲区中的当前位置。最初,它在创建缓冲区时设置为 0。换句话说,它是下一个要读取或写入的元素的索引。
该标记用于保存光标位置。当我们操作缓冲区时,游标位置不断变化,但我们总能将其返回到先前标记的位置。
使用缓冲区的方法
现在让我们看看主要的方法集,这些方法让我们可以使用我们的缓冲区(内存块)从通道读取数据和从通道写入数据。
-
allocate(int capacity) — 此方法用于分配具有指定容量的新缓冲区。如果传递的容量是负整数,allocate ()方法将抛出IllegalArgumentException 。
-
capacity()返回当前缓冲区的容量。
-
position()返回当前光标位置。读写操作将光标移动到缓冲区的末尾。返回值始终小于或等于限制。
-
limit()返回当前缓冲区的限制。
-
mark()用于标记(保存)当前光标位置。
-
reset()将光标返回到先前标记(保存)的位置。
-
clear()将位置设置为零并将限制设置为容量。此方法不会清除缓冲区中的数据。它只会重新初始化位置、限制和标记。
-
flip()将缓冲区从写模式切换到读模式。它还将限制设置为当前位置,然后将位置放回零。
-
read() — 通道的 read 方法用于将数据从通道写入缓冲区,而缓冲区的put()方法用于将数据写入缓冲区。
-
write() — 通道的 write 方法用于将数据从缓冲区写入通道,而缓冲区的get()方法用于从缓冲区读取数据。
-
rewind()倒回缓冲区。当您需要重新读取缓冲区时使用此方法 - 它将位置设置为零并且不更改限制。
现在谈谈渠道。
Java NIO 中最重要的通道实现是以下类:
-
FileChannel — 用于从文件读取数据和向文件写入数据的通道。
-
DatagramChannel — 此类通过 UDP(用户数据报协议)在网络上读取和写入数据。
-
SocketChannel — 通过 TCP(传输控制协议)通过网络读取和写入数据的通道。
-
ServerSocketChannel — 一种通过 TCP 连接读取和写入数据的通道,就像 Web 服务器一样。为每个传入连接创建一个SocketChannel 。
实践
是时候写几行代码了。首先,让我们读取文件并将其内容显示在控制台上,然后将一些字符串写入文件。
代码包含很多注释——我希望它们能帮助你理解一切是如何工作的:
// Create a RandomAccessFile object, passing in the file path
// and a string that says the file will be opened for reading and writing
try (RandomAccessFile randomAccessFile = new RandomAccessFile("text.txt", "rw");
// Get an instance of the FileChannel class
FileChannel channel = randomAccessFile.getChannel();
) {
// Our file is small, so we'll read it in one go
// Create a buffer of the required size based on the size of our channel
ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
// Read data will be put into a StringBuilder
StringBuilder builder = new StringBuilder();
// Write data from the channel to the buffer
channel.read(byteBuffer);
// Switch the buffer from write mode to read mode
byteBuffer.flip();
// In a loop, write data from the buffer to the StringBuilder
while (byteBuffer.hasRemaining()) {
builder.append((char) byteBuffer.get());
}
// Display the contents of the StringBuilder on the console
System.out.println(builder);
// Now let's continue our program and write data from a string to the file
// Create a string with arbitrary text
String someText = "Hello, Amigo!!!!!";
// Create a new buffer for writing,
// but let the channel remain the same, because we're going to the same file
// In other words, we can use one channel for both reading and writing to a file
// Create a buffer specifically for our string — convert the string into an array and get its length
ByteBuffer byteBuffer2 = ByteBuffer.allocate(someText.getBytes().length);
// Write our string to the buffer
byteBuffer2.put(someText.getBytes());
// Switch the buffer from write mode to read mode
// so that the channel can read from the buffer and write our string to the file
byteBuffer2.flip();
// The channel reads the information from the buffer and writes it to our file
channel.write(byteBuffer2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
试试NIO API——你会爱上它的!