早些时候,我们了解了IO API(输入/输出应用程序编程接口)和java.io包,它们的类主要用于处理 Java 中的流。这里的关键是流的概念。

今天我们将开始考虑NIO API(新输入/输出)。

这两种 I/O 方法的主要区别在于 IO API 是面向流的,而 NIO API 是面向缓冲区的。所以要理解的主要概念是bufferschannels

什么是缓冲区,什么是通道?

通道是数据进出的逻辑入口,而缓冲区是此传输数据的源或目的地在输出过程中,将要发送的数据放入缓冲区,缓冲区将数据传递给通道。在输入期间,来自通道的数据被放入缓冲区。

换句话说:

  • 缓冲区只是一块内存,我们可以在其中写入信息,也可以从中读取信息
  • 通道是提供对 I/O 设备(例如文件或套接字)的访问的网关

通道与 java.io 包中的流非常相似。所有去往任何地方(或来自任何地方)的数据都必须通过通道对象。通常,要使用 NIO 系统,您会获得一个到 I/O 实体的通道和一个用于存储数据的缓冲区。然后您使用缓冲区,根据需要输入或输出数据。

您可以在缓冲区中前后移动,即“遍历”缓冲区,这是您在流中无法做到的事情。这在处理数据时提供了更大的灵活性。在标准库中,缓冲区由抽象Buffer类及其几个后代表示:

  • 字节缓冲区
  • 字符缓冲区
  • 短缓冲区
  • 缓冲区
  • 浮动缓冲区
  • 双缓冲
  • 长缓冲区

子类之间的主要区别在于它们存储的数据类型——字节整数长整型和其他原始数据类型。

缓冲区属性

缓冲区有四个主要属性。这些是容量、限制、位置和标记。

容量是可以存储在缓冲区中的最大数据量/字节数。缓冲区的容量无法更改。一旦缓冲区已满,必须先清除缓冲区,然后再向其写入更多内容。

在写入模式下,缓冲区的限制与其容量相同,表示可以写入缓冲区的最大数据量。在读取模式下,缓冲区的限制是指可以从缓冲区读取的最大数据量。

position指示光标在缓冲区中的当前位置最初,它在创建缓冲区时设置为 0。换句话说,它是下一个要读取或写入的元素的索引。

标记用于保存光标位置。当我们操作缓冲区时,游标位置不断变化,但我们总能将其返回到先前标记的位置。

使用缓冲区的方法

现在让我们看看主要的方法集,这些方法让我们可以使用我们的缓冲区(内存块)从通道读取数据和从通道写入数据。

  1. allocate(int capacity) — 此方法用于分配具有指定容量的新缓冲区。如果传递的容量是负整数,allocate ()方法将抛出IllegalArgumentException 。

  2. capacity()返回当前缓冲区的容量

  3. position()返回当前光标位置。读写操作将光标移动到缓冲区的末尾。返回值始终小于或等于限制。

  4. limit()返回当前缓冲区的限制。

  5. mark()用于标记(保存)当前光标位置。

  6. reset()将光标返回到先前标记(保存)的位置。

  7. clear()将位置设置为零并将限制设置为容量。此方法不会清除缓冲区中的数据。它只会重新初始化位置、限制和标记。

  8. flip()将缓冲区从写模式切换到读模式。它还将限制设置为当前位置,然后将位置放回零。

  9. read() — 通道的 read 方法用于将数据从通道写入缓冲区,而缓冲区的put()方法用于将数据写入缓冲区。

  10. write() — 通道的 write 方法用于将数据从缓冲区写入通道,而缓冲区的get()方法用于从缓冲区读取数据。

  11. rewind()倒回缓冲区。当您需要重新读取缓冲区时使用此方法 - 它将位置设置为零并且不更改限制。

现在谈谈渠道。

Java NIO 中最重要的通道实现是以下类:

  1. FileChannel — 用于从文件读取数据和向文件写入数据的通道。

  2. DatagramChannel — 此类通过 UDP(用户数据报协议)在网络上读取和写入数据。

  3. SocketChannel — 通过 TCP(传输控制协议)通过网络读取和写入数据的通道。

  4. 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——你会爱上它的!