1.简介
Java NIO是从Java 1.4引入的库。 自从Java NIO推出以来,它提供了另一种方法来处理I / O和网络事务。 它被认为是Java网络和Java IO库的替代方法。 开发Java NIO的目的是使输入和输出的事务异步和非阻塞。 阻塞和非阻塞IO的概念将在后面的部分中介绍。
目录
- 1.简介 2. IO中的术语
- 2.1阻止输入和输出 2.2无阻塞输入和输出
3. Java NIO术语 - 3.1缓冲区
- 3.1.1缓冲区属性 3.1.2读取和写入缓冲区 3.1.3标记并重置缓冲区
3.2频道 - 3.2.1通道分散和聚集 3.2.2频道转移
3.3选择器
4. NIO频道 - 4.1。 文件频道 4.2 SocketChannel 4.3 ServerSocketChannel
5.结论
2. Java NIO中的术语
Java NIO在使用Java的I / O处理中引入了许多新术语。 在较早的场景中,Java I / O基于字符流和字节流。 但是,使用Java NIO,现在可以通过通道读取和处理数据-通过Channel进入缓冲区或通过缓冲区进入通道。 在本节中,我们将讨论与Java NIO相关的各种术语,以帮助我们更好地理解进一步的教程。
2.1阻止输入和输出
使用字符流和字节流,文件被加载到JVM内存中并被锁定以进行读取或写入。 这导致其他试图读取同一文件的程序处于阻塞状态。 尽管有可用的处理能力,但由于程序被迫等待,因此这种情况浪费了CPU的能力。 读取或写入数据的这种安排称为阻塞输入和输出。
2.2无阻塞输入和输出
随着无阻塞输入和输出进入画面,数据开始被读取到通道中。 在无阻塞的输入和输出布置中,JVM使用通道来缓冲数据。 这些通道允许动态读取数据,而不会阻止文件供外部使用。 通道是一块缓冲的内存,一旦读取了先前的缓冲数据,就将其填充。 这样可以确保在完整的读取周期中不会阻止该文件,并且其他程序也可以对该文件进行必要的访问。
Java NIO主要涉及三个术语:
- 频道
- 选择器
- 缓冲液
这些术语将在本文中进一步声明。
3. Java NIO –术语
如上所述,Java非阻塞IO在通道和缓冲区上工作。 在本节中,我们将尝试理解这些术语以及其他术语选择器。 这些术语对于遵循后续教程很重要。
3.1缓冲区
缓冲区是固定的一块内存,用于在将数据读入通道之前存储该数据。 缓冲区可确保预定义大小的数据,以加快文件,输入和数据流的读取速度。 缓冲区的大小可配置为2到幂n的块。
根据输入类型,可以使用各种类型的缓冲区:
- ByteBuffer:用于按字节读取字符流或文件
- CharBuffer:用于读取完整ASCII集内的字符
- DoubleBuffer:专门用于双重数据值,例如来自传感器的读数
- FloatBuffer:用于读取恒定数据流,用于分析之类的目的
- LongBuffer:用于读取长数据类型的值
- IntBuffer:用于读取分数或结果的整数值
- ShortBuffer:用于读取短整数值
每个缓冲区都有其特定用途。 通常用于文件的缓冲区是ByteBuffer和CharBuffer。 创建字节缓冲区的简短示例如下所示。
RandomAccessFile aFile = new RandomAccessFile("src/data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
在上面的代码中,正在创建48个字节的缓冲区。 必须指定缓冲区大小。 第三行将48个字节的存储空间分配给缓冲区buf。 这样可以确保将必要的内存预先分配给缓冲区。 在继续使用它们进行通道读写之前,必须先了解缓冲区的读写过程。 下图显示了从文件读取字节的典型缓冲区。
可以看出,缓冲区读取并向左推送第一个字节。 缓冲区是后进先出类型的内存分配。 因此,当您希望使用缓冲区读取文件时,必须先将其翻转,然后才能读取文件。 没有翻转,数据将以相反的顺序输出。 为了翻转缓冲区,需要执行以下简单代码行:
buf.flip();
一旦将数据读入缓冲区,就该实际获取已读取的数据了。 为了读取数据,使用了buf.get()
函数。 此函数调用在每次调用时都读取一个字节/字符/数据包,具体取决于缓冲区的类型。 读取可用数据后,还必须在下一次读取之前清理缓冲区。 必须进行清理以确保释放空间以读取更多数据。 为了清洗缓冲区,有两种可行的方法–清除缓冲区或压缩缓冲区。
要清除缓冲区,请执行命令buf.clear()
。 要压缩缓冲区,请使用命令buf.compact()
。 这两个命令最终都会做同样的事情。 但是, compact()
仅清除使用函数buf.get()
读取的数据。 因此,在需要继续优化缓冲区使用的内存量时使用它。
3.1.1缓冲区属性
缓冲区本质上具有3个属性:
- 缓冲位置
- 缓冲区限制
- 缓冲能力
在缓冲区写入期间,缓冲区位置是当前字节正在写入的位置。 在缓冲区读取过程中,缓冲区位置是从中读取字节的位置。 随着我们进行读取或写入操作,缓冲区位置不断动态变化。
在缓冲区写入期间,缓冲区限制是可以写入缓冲区的最大数据大小。 本质上,缓冲区限制和缓冲区容量在缓冲区写入期间是同义词。 但是,在缓冲区读取期间,缓冲区限制是可从缓冲区读取的可用字节数。 因此,当弹出字节时,缓冲区限制将继续减小。
缓冲区容量是可以在任何时间点写入缓冲区或从缓冲区读取的最大数据。 因此,上面分配的大小48也称为缓冲区容量。
3.1.2读取和写入缓冲区
缓冲区本质上是一种存储数据的介质,直到线程从缓冲区中读取数据并请求新数据为止。 从输入源读取数据的主要步骤是将数据读取到缓冲区中。 为了将数据读入缓冲区,使用了下面显示的代码段。
FileChannel inChannel = aFile.getChannel();
System.out.println("Created file Channel..");
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
在上面的代码中,我们创建一个FileChannel
从文件中读取数据,并创建一个缓冲区buf
来保存数据。 然后,使用语句inChannel.read(buf)
将缓冲区用于读取通道的数据。 执行此语句后,缓冲区现在将保留多达48个字节的可用数据。
为了开始读取数据,您使用了一个简单的语句buf.get()
。
3.1.3标记并重置缓冲区
在读取过程中,经常有一些情况需要重复从特定位置读取数据。 在正常情况下,一旦从缓冲区中获取数据,就认为该数据已消失。 但是,可以在特定的索引处标记缓冲区,从而可以再次从特定位置读取缓冲区。 下面的代码演示了如何完成此操作。
buffer.mark();char x = buffer.get();
char y = buffer.get();
char z = buffer.get();
char a = buffer.get();
//Do something with above databuffer.reset(); //set position back to mark.
标记和重置的主要应用是重复分析数据,重复重复信息,发送命令特定次数甚至更多。
3.2频道
通道是非阻塞IO的主要介质。 通道类似于可用于阻止IO的流。 这些通道支持网络数据以及文件数据IO。 当需要时,通道从缓冲区读取数据。 缓冲区保存数据,直到从缓冲区读取数据为止。
通道具有多种实现方式,具体取决于要读取的数据。 以下列出了可用于渠道的标准实现:
- FileChannel:用于从文件读取数据和向文件写入数据
- DatagramChannel:用于使用UDP数据包通过网络进行数据交换
- SocketChannel:用于通过TCP套接字交换数据的TCP通道
- ServerSocketChannel:类似于Web服务器的实现,它通过特定的TCP端口侦听请求。 它为每个新连接创建一个新的
SocketChannel
实例
从通道名称可以理解,除了文件IO之外,它们还涵盖UDP + TCP网络IO流量。 与可以在特定时刻读取或写入的Streams不同,同一Channel可以无缝读取和写入资源。 通道支持异步读写,这确保在不妨碍代码执行的情况下读取数据。 上面讨论的缓冲区支持通道的这种异步操作。
3.2.1通道分散和聚集
Java NIO固有地支持数据分散和收集,以便从多个缓冲区读取数据或将数据写入多个缓冲区。 Java NIO非常智能,可以管理多个缓冲区中的读取和写入。
Java NIO分散用于将通道中的读取分散到多个缓冲区中。 它的代码实现非常简单。 您需要做的就是添加一个缓冲区数组作为读取的参数。 下面显示了相同的代码段。
ByteBuffer buffer1 = ByteBuffer.allocate(128);
ByteBuffer buffer2 = ByteBuffer.allocate(128);
ByteBuffer[] buffers = {buffer1,buffer2};channel.read(buffers);
在上面的代码中,我们创建了两个每个128字节的缓冲区。 注意这里我们创建了一个包含两个缓冲区的数组。 该数组进一步作为参数传递给读取的通道。 通道将数据读入第一个缓冲区,直到达到缓冲区容量。 一旦达到缓冲区容量,通道将自动切换到下一个缓冲区。 因此,读取的通道被分散而对线程没有任何影响。
Java NIO收集也以类似的方式工作。 读取到多个缓冲区的数据也可以收集并写入单个通道。 下面的代码片段做了类似的事情。
ByteBuffer buffer1 = ByteBuffer.allocate(128);
ByteBuffer buffer2 = ByteBuffer.allocate(128);
ByteBuffer[] buffers = {buffer1,buffer2};channel.write(buffers);
该代码类似于分散读取。 需要理解的是信息的写入顺序。 该信息从第一个缓冲区开始写入通道。 达到第一个缓冲区的限制后,通道会自动切换到下一个缓冲区。 重要的是要注意在此写入过程中不会发生翻转。 如果需要在写入之前翻转缓冲区,则需要在将缓冲区分配给数组之前完成。
3.2.2频道转移
顾名思义,通道传输是将数据从一个通道传输到另一个通道的过程。 可以从通道缓冲区的特定位置进行通道传输。 但是,在位置值设置为零的情况下,可以将完整的输入源复制或复制到指定的输出目标。 例如,在关键字和文本编辑器之间建立通道,将使您能够将输入从键盘连续传输到文本编辑器。
为了促进通道传输,Java NIO配备了两个函数,即transferFrom()
和transferTo()
。 这些功能的用途从它们的标识符中很清楚。 让我们以一个例子来更好地理解这些功能。
我们将使用上面创建的data.txt文件作为输入源。 我们会将数据从该文件传输到新文件output.txt 。 下面的代码使用transferFrom()
方法调用执行相同的操作。
ChannelTransfer.java
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;public class ChannelTransfer {public static void main(String[] args) {try {RandomAccessFile copyFrom = new RandomAccessFile("src/data.txt", "rw");FileChannel fromChannel = copyFrom.getChannel();RandomAccessFile copyTo = new RandomAccessFile("src/output.txt", "rw");FileChannel toChannel = copyTo.getChannel();long count = fromChannel.size();toChannel.transferFrom(fromChannel, 0, count);} catch (Exception e) {System.out.println("Error: " + e);}}
}
从上面的代码中可以看出,通道fromChannel
用于从data.txt中读取数据。 toChannel
用于从位置0开始的fromChannel
获取数据。重要的是要注意,整个文件都是使用FileChannel
复制的。 但是,在SocketChannel
某些实现中,情况可能并非如此。 在这种情况下,仅复制在传输时可在缓冲区中读取的数据。 此行为归因于SocketChannel
实现的动态性质。
transferTo
的实现非常相似。 唯一需要做的更改是方法调用将使用源对象完成,而目标通道对象将是方法调用中的一个参数。
3.3选择器
顾名思义,选择器用于从多个通道中选择一个通道。 当您计划使用单个线程并行管理多个资源时,Java NIO中的选择器特别有用。 选择器充当线程和开放通道之间的桥梁。 选择器通常在预期线程流量较低但需要使用多个资源的情况下使用。 下面描述了角色选择器可能扮演的角色的示意图。
选择器的创建非常简单。 下面的代码段说明了如何创建选择器以及如何在选择器中注册频道。
Selector myFirstSelector = Selector.open(); //opens the selector
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
SelectionKey selectorKey = channel.register(myFirstSelector, SelectionKey.OP_READ);
上面的代码创建一个SocketChannel
,将其配置为非阻塞并将其注册到选择器。 请注意,我们使用了SocketChannel
。 选择器需要一个可以配置为非阻塞的通道。 因此,选择器不能与FileChannel
一起使用。
需要注意的另一点是注册SocketChannel
的第二个参数。 参数指定我们要监视的通道事件。 选择器等待事件并在触发事件时更改其状态。 这些事件在下面列出:
- 连接
- 接受
- 读
- 写
可以使用如下所示的静态常量来配置这些事件:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
选择器为我们提供了预定义的功能,以检查这些事件的发生。 下面的方法调用是不言自明的,可用于监视这些事件的发生。
key.isAcceptable();
key.isConnectable();
key.isReadable();
key.isWritable();
因此,当我们计划用较少的线程管理多个资源时,选择器非常有用。
4. NIO频道
4.1文件通道
这种类型的通道通常用于读取和写入文件。 下面显示了用于创建通道并将其用于读写的示例代码
ChannelRW.java
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class ChannelRW {public static void main(String[] args) {System.out.println("Starting the file read..");try {RandomAccessFile aFile = new RandomAccessFile("src/data.txt", "rw");FileChannel inChannel = aFile.getChannel();System.out.println("Created file Channel..");ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf);while (bytesRead != -1) {System.out.println("\nRead " + bytesRead);buf.flip();while(buf.hasRemaining()){System.out.print((char) buf.get());}buf.clear();bytesRead = inChannel.read(buf);}aFile.close();}catch(Exception e) {System.out.println("Error:"+e);}}
}
data.txt
Hello,
This is my first NIO read. I am reading multiple lines.
The code is implemented for Javacodegeeks by Abhishek Kothari
在上面的代码中,我们试图读取如上所示创建的data.txt
文件。 正在使用Java NIO库读取该文件。 文件读取过程涉及多个步骤。 下面显示了针对它的逐步代码说明。
- 使用
RandomAccessFile
对象打开文件。 这样可以确保文件不会被阻止访问。 类名建议,仅在需要时才允许随机访问文件。 因此,IO本质上是非阻塞的 - 从上面创建的对象中获取
FileChannel
对象。 该通道有助于根据需要从文件中读取数据。 通道是文件和缓冲区之间的一种隧道设置 - 创建一个缓冲区来存储从通道读取的字节。 注意,我们已经指定了缓冲区大小。 因此,将始终以48个字节的块读取数据。
- 从缓冲区读取数据,直到读取的字节数变为负数为止。
- 翻转缓冲区后,打印已读取的数据。 上面已经解释了缓冲器翻转的原因。
- 关闭文件对象以防止任何形式的内存泄漏
上面代码的输出如下所示:
Starting the file read..
Created file Channel..Read 48
Hello,
This is my first NIO read. I am reading m
Read 48
ultiple lines.
The code is implemented for Javac
Read 28
odegeeks by Abhishek Kothari
可见,数据以48个字节的块读取,并且每次出现“ Read 48
”语句以显示已读取的字节数。 可以看出,当到达文件末尾时,只能读取28个字节,因此返回计数28。
4.2 SocketChannel
这种类型的通道用于连接到http套接字。 连接到套接字的简单代码如下所示:
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("https://javacodegeeks.com", 8080));
套接字通道在执行方法调用时连接到指定的URL。 为了关闭打开的通道,只需执行如下所示的相应方法调用即可。
sc.close();
从SocketChannel
读取数据的过程类似于FileChannel
。
4.3 ServerSocketChannel
服务器套接字通道用于读取来自套接字客户端的套接字数据。 可以使用以下代码段将该通道配置为读取。
ServerSocketChannel ssc = ServerSocketChannel.open();ssc.socket().bind(new InetSocketAddress(8080));while(true){SocketChannel sc =ssc.accept();//do something with sc...
}
上面的代码打开服务器端套接字,并允许来自外部SocketChannel
客户端的套接字连接。 从上面的代码可以理解, ServerSocketChannel
仅需要端口号即可启动套接字服务器。 一旦启动,它可以创建自己的SocketChannel
来接受来自其他套接字的数据。 这就是使用Java NIO进行无阻塞Socket IO连接的方式。
5.结论
本文详细讨论了Java NIO的各种术语。 它说明了使用各种NIO通道以非阻塞方式读取和写入数据的过程。 关于Java NIO库(如DatagramChannel,Pipes,Async通道等),还有更多需要探索的地方。
翻译自: https://www.javacodegeeks.com/2018/07/java-nio-tutorial.html