- Java NIO Tutorial
- Java NIO - Home
- Java NIO - Overview
- Java NIO - Environment Setup
- Java NIO vs JAVA IO
- Java NIO - Channels
- Java NIO - File Channel
- Java NIO - DataGram Channel
- Java NIO - Socket Channel
- Java NIO - Server Socket Channel
- Java NIO - Scatter
- Java NIO - Gather
- Java NIO - Buffer
- Java NIO - Selector
- Java NIO - Pipe
- Java NIO - Path
- Java NIO - File
- Java NIO - AsynchronousFileChannel
- Java NIO - CharSet
- Java NIO - FileLock
- Java NIO Useful Resources
- Java NIO - Quick Guide
- Java NIO - Useful Resources
- Java NIO - Discussion
Java NIO - 快速指南
Java NIO - 概述
java.nio包是在java 1.4中引入的。与 java NIO 中的 java I/O 相比,引入了面向缓冲区和通道的 I/O 操作数据流,从而提供了更快的执行速度和更好的性能。
NIO API 还提供选择器,它引入了以异步或非阻塞方式侦听 IO 事件的多个通道的功能。在 NIO 中,最耗时的 I/O 活动包括向操作系统填充和排出缓冲区,从而提高速度。
NIO API 的中心抽象如下:
缓冲区是数据、字符集及其关联的解码器和编码器的容器,在字节和 Unicode 字符之间进行转换。
各种类型的通道,表示与能够执行 I/O 操作的实体的连接
选择器和选择键与可选通道一起定义了多路复用、非阻塞 I/O 设施。
Java NIO - 环境设置
本节将指导您如何在计算机上下载并设置 Java。请按照以下步骤设置环境。
Java SE 可以通过下载 Java链接免费获得。因此,您可以根据您的操作系统下载一个版本。
按照说明下载 java 并运行 .exe以在您的计算机上安装 Java。在计算机上安装 Java 后,您需要设置环境变量以指向正确的安装目录 -
设置 Windows 2000/XP 的路径
假设您已将 Java 安装在c:\Program Files\java\jdk目录中 -
右键单击“我的电脑”并选择“属性”。
单击“高级”选项卡下的“环境变量”按钮。
现在更改“Path”变量,使其也包含 Java 可执行文件的路径。例如,如果路径当前设置为“C:\WINDOWS\SYSTEM32”,则将路径更改为“C:\WINDOWS\SYSTEM32;c:\Program Files\java\jdk\bin”。
设置Windows 95/98/ME的路径
假设您已将 Java 安装在c:\Program Files\java\jdk目录中 -
编辑“C:\autoexec.bat”文件并在末尾添加以下行:
“SET PATH = %PATH%;C:\Program Files\java\jdk\bin”
设置 Linux、UNIX、Solaris、FreeBSD 的路径
环境变量 PATH 应设置为指向 java 二进制文件的安装位置。如果您在执行此操作时遇到问题,请参阅您的 shell 文档。
例如,如果您使用bash作为 shell,那么您可以将以下行添加到 '.bashrc:export PATH = /path/to/java:$PATH' 的末尾
流行的 Java 编辑器
要编写 Java 程序,您需要一个文本编辑器。市场上还有更复杂的 IDE。但现在,您可以考虑以下其中一项 -
记事本- 在 Windows 计算机上,您可以使用任何简单的文本编辑器,例如记事本(本教程推荐)、TextPad。
Netbeans - 是一个开源且免费的 Java IDE,可以从http://www.netbeans.org/index.html下载。
Eclipse - 也是由 eclipse 开源社区开发的 java IDE,可以从https://www.eclipse.org/下载。
Java NIO 与 IO
我们知道,java NIO 是为了改进传统的 java IO API 而引入的。使 NIO 比 IO 更高效的主要增强是 NIO 中使用的通道数据流模型以及对传统 IO 任务使用操作系统。
Java NIO 和 Java IO 之间的区别可以解释如下 -
正如前一篇文章中提到的,NIO 缓冲区和面向 I/O 操作的通道数据流与 IO 相比,可提供更快的执行速度和更好的性能。此外,NIO 使用操作系统来执行传统的 I/O 任务,这再次使其更加高效。
NIO 和 IO 之间的另一个区别是,IO 使用流线数据流,即一次多一个字节,并依赖于将数据对象转换为字节,反之亦然,而 NIO 处理的是字节块的数据块。
在java中,IO流对象是单向的,而在NIO中,通道是双向的,这意味着通道可以用于读取和写入数据。
IO 中的流线型数据流不允许数据来回移动。如果需要从流中读取数据来回移动,则需要先将其缓存在缓冲区中。而在 NIO 中,我们使用面向缓冲区它允许来回访问数据而无需缓存。
NIO API 还支持多线程,以便可以异步读写数据,这样在执行 IO 操作时当前线程不会被阻塞。这再次使其比传统的 java IO API 更加高效。
Java NIO 中选择器的引入引入了多线程的概念,它允许以异步或非阻塞的方式监听 IO 事件的多个通道。
NIO 中的多线程使其成为非阻塞,这意味着仅当数据可用时才请求线程读取或写入,否则线程可以同时用于其他任务。但这在传统 java IO 的情况下是不可能的,因为没有多线程它受支持,使其成为阻塞。
NIO 允许仅使用单个线程管理多个通道,但代价是解析数据可能比在 java IO 的情况下从阻塞流读取数据更复杂。因此,如果需要较少的连接和非常高的带宽一次发送大量数据,在这种情况下 java IO API 可能是最合适的。
Java NIO - 通道
描述
顾名思义,通道用作从一端到另一端的数据流的手段。在 Java NIO 中,通道在缓冲区和另一端的实体之间的作用相同,换句话说,通道用于将数据读取到缓冲区以及从缓冲区写入数据。
与传统 Java IO 通道中使用的流不同,它有两种方式,即可以读取也可以写入。Java NIO 通道支持阻塞和非阻塞模式下的异步数据流。
通道的实现
Java NIO 通道主要在以下类中实现 -
FileChannel - 为了从文件中读取数据,我们使用文件通道。文件通道对象只能通过调用文件对象的 getChannel() 方法来创建,因为我们不能直接创建文件对象。
DatagramChannel - 数据报通道可以通过 UDP(用户数据报协议)通过网络读取和写入数据。可以使用工厂方法创建 DataGramchannel 的对象。
SocketChannel - SocketChannel 通道可以通过 TCP(传输控制协议)通过网络读取和写入数据。它还使用工厂方法来创建新对象。
ServerSocketChannel - ServerSocketChannel 通过 TCP 连接读取和写入数据,与 Web 服务器相同。对于每个传入连接,都会创建一个 SocketChannel。
例子
以下示例从C:/Test/temp.txt中的文本文件读取并将内容打印到控制台。
临时文件.txt
Hello World!
ChannelDemo.java
import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class ChannelDemo { public static void main(String args[]) throws IOException { RandomAccessFile file = new RandomAccessFile("C:/Test/temp.txt", "r"); FileChannel fileChannel = file.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(512); while (fileChannel.read(byteBuffer) > 0) { // flip the buffer to prepare for get operation byteBuffer.flip(); while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } } file.close(); } }
输出
Hello World!
Java NIO - 文件通道
描述
正如已经提到的,Java NIO 通道的 FileChannel 实现被引入来访问文件的元数据属性,包括创建、修改、大小等。此外,文件通道是多线程的,这再次使得 Java NIO 比 Java IO 更高效。
一般来说,我们可以说FileChannel是一个连接到文件的通道,通过它可以从文件中读取数据,并向文件中写入数据。FileChannel的另一个重要特性是它不能设置为非阻塞模式并且始终以阻塞模式运行。
我们无法直接获取文件通道对象,文件通道对象可以通过以下方式获取:
getChannel() - 任何 FileInputStream、FileOutputStream 或 RandomAccessFile 上的方法。
open() - 文件通道的方法,默认情况下打开通道。
文件通道的对象类型取决于对象创建时调用的类的类型,即,如果通过调用 FileInputStream 的 getchannel 方法创建对象,则文件通道将打开以供读取,并且在尝试写入时将抛出 NonWritableChannelException。
例子
以下示例展示了如何从 Java NIO FileChannel 读取和写入数据。
以下示例从C:/Test/temp.txt中的文本文件读取并将内容打印到控制台。
临时文件.txt
Hello World!
文件通道演示.java
import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.HashSet; import java.util.Set; public class FileChannelDemo { public static void main(String args[]) throws IOException { //append the content to existing file writeFileChannel(ByteBuffer.wrap("Welcome to TutorialsPoint".getBytes())); //read the file readFileChannel(); } public static void readFileChannel() throws IOException { RandomAccessFile randomAccessFile = new RandomAccessFile("C:/Test/temp.txt", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(512); Charset charset = Charset.forName("US-ASCII"); while (fileChannel.read(byteBuffer) > 0) { byteBuffer.rewind(); System.out.print(charset.decode(byteBuffer)); byteBuffer.flip(); } fileChannel.close(); randomAccessFile.close(); } public static void writeFileChannel(ByteBuffer byteBuffer)throws IOException { Set<StandardOpenOption> options = new HashSet<>(); options.add(StandardOpenOption.CREATE); options.add(StandardOpenOption.APPEND); Path path = Paths.get("C:/Test/temp.txt"); FileChannel fileChannel = FileChannel.open(path, options); fileChannel.write(byteBuffer); fileChannel.close(); } }
输出
Hello World! Welcome to TutorialsPoint
Java NIO - 数据报通道
Java NIO 数据报用作通道,可以通过无连接协议发送和接收 UDP 数据包。默认情况下,数据报通道是阻塞的,但可以在非阻塞模式下使用。为了使其成为非阻塞,我们可以使用 configureBlocking( false) 方法。DataGram 通道可以通过调用其名为open()的静态方法之一来打开,该方法也可以将 IP 地址作为参数,以便可以用于多播。
与 FileChannel 类似的数据报通道默认情况下不会连接,为了使其连接,我们必须显式调用其 connect() 方法。但是,数据报通道不需要连接,以便在必须连接时使用发送和接收方法为了使用读取和写入方法,因为这些方法不接受或返回套接字地址。
我们可以通过调用其isConnected()方法来检查数据报通道的连接状态。一旦连接,数据报通道将保持连接状态,直到断开或关闭。数据报通道是线程安全的,支持多线程和并发。
数据报通道的重要方法
bind(SocketAddress local) - 此方法用于将数据报通道的套接字绑定到本地地址,该地址作为此方法的参数提供。
connect(SocketAddress remote) - 此方法用于将套接字连接到远程地址。
connect() - 此方法用于断开与远程地址的套接字。
getRemoteAddress() - 此方法返回通道套接字连接的远程位置的地址。
isConnected() - 正如已经提到的,此方法返回数据报通道的连接状态,即是否已连接。
open() 和 open(ProtocolFamily family) - Open 方法用于为单个地址打开数据报通道,而参数化 open 方法用于为表示为协议族的多个地址打开通道。
read(ByteBuffer dst) - 此方法用于通过数据报通道从给定缓冲区读取数据。
receive(ByteBuffer dst) - 此方法用于通过此通道接收数据报。
send(ByteBuffer src, SocketAddress target) - 此方法用于通过此通道发送数据报。
例子
以下示例展示了如何从 Java NIO DataGramChannel 发送数据。
服务器:DatagramChannelServer.java
import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; public class DatagramChannelServer { public static void main(String[] args) throws IOException { DatagramChannel server = DatagramChannel.open(); InetSocketAddress iAdd = new InetSocketAddress("localhost", 8989); server.bind(iAdd); System.out.println("Server Started: " + iAdd); ByteBuffer buffer = ByteBuffer.allocate(1024); //receive buffer from client. SocketAddress remoteAdd = server.receive(buffer); //change mode of buffer buffer.flip(); int limits = buffer.limit(); byte bytes[] = new byte[limits]; buffer.get(bytes, 0, limits); String msg = new String(bytes); System.out.println("Client at " + remoteAdd + " sent: " + msg); server.send(buffer,remoteAdd); server.close(); } }
输出
Server Started: localhost/127.0.0.1:8989
客户端:DatagramChannelClient.java
import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; public class DatagramChannelClient { public static void main(String[] args) throws IOException { DatagramChannel client = null; client = DatagramChannel.open(); client.bind(null); String msg = "Hello World!"; ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); InetSocketAddress serverAddress = new InetSocketAddress("localhost", 8989); client.send(buffer, serverAddress); buffer.clear(); client.receive(buffer); buffer.flip(); client.close(); } }
输出
运行客户端将在服务器上打印以下输出。
Server Started: localhost/127.0.0.1:8989 Client at /127.0.0.1:64857 sent: Hello World!
Java NIO - 套接字通道
Java NIO 套接字通道是一个可选类型通道,这意味着它可以使用选择器进行复用,用于连接套接字的面向流的数据流。套接字通道可以通过调用其静态 open() 方法来创建,前提是任何预先存在的套接字尚未存在目前。套接字通道是通过调用 open 方法创建的,但尚未连接。为了连接套接字通道,需要调用connect()方法。这里需要提到的一点是,如果通道未连接并且尝试任何 I/O 操作如果要尝试,则该通道会抛出 NotYetConnectedException。因此,在执行任何 IO 操作之前必须确保通道已连接。通道连接后,它将保持连接状态,直到关闭。套接字通道的状态可以通过调用来确定它的isConnected方法。
套接字通道的连接可以通过调用其finishConnect()方法来完成。通过调用 isConnectionPending 方法可以确定是否有连接操作正在进行。套接字通道默认支持非阻塞连接。并且支持异步关闭,这类似于Channel类中指定的异步关闭操作。
套接字通道可供多个并发线程安全使用。它们支持并发读取和写入,但在任何给定时间最多一个线程可以读取并且最多一个线程可以写入。connect 和 finishConnect 方法相互同步,并且在调用这些方法之一时尝试启动读取或写入操作将被阻塞,直到该调用完成。
Socket通道的重要方法
bind(SocketAddress local) - 此方法用于将套接字通道绑定到本地地址,该地址作为此方法的参数提供。
connect(SocketAddress remote) - 此方法用于将套接字连接到远程地址。
finishConnect() - 此方法用于完成连接套接字通道的过程。
getRemoteAddress() - 此方法返回通道套接字连接的远程位置的地址。
isConnected() - 正如已经提到的,此方法返回套接字通道的连接状态,即是否已连接。
open() 和 open((SocketAddress remote) - Open 方法用于打开没有指定地址的套接字通道,而参数化 open 方法用于指定远程地址打开通道并连接到它。这种便捷方法的工作原理就像调用 open( ) 方法,在生成的套接字通道上调用 connect 方法,将其传递到远程,然后返回该通道。
read(ByteBuffer dst) - 此方法用于通过套接字通道从给定缓冲区读取数据。
isConnectionPending() - 此方法告知此通道上是否正在进行连接操作。
例子
以下示例展示了如何从 Java NIO SocketChannel 发送数据。
C:/测试/temp.txt
Hello World!
客户端:SocketChannelClient.java
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.EnumSet; public class SocketChannelClient { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocket = null; SocketChannel client = null; serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); client = serverSocket.accept(); System.out.println("Connection Set: " + client.getRemoteAddress()); Path path = Paths.get("C:/Test/temp1.txt"); FileChannel fileChannel = FileChannel.open(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE) ); ByteBuffer buffer = ByteBuffer.allocate(1024); while(client.read(buffer) > 0) { buffer.flip(); fileChannel.write(buffer); buffer.clear(); } fileChannel.close(); System.out.println("File Received"); client.close(); } }
输出
在服务器启动之前,运行客户端不会打印任何内容。
服务器:SocketChannelServer.java
import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; import java.nio.file.Path; import java.nio.file.Paths; public class SocketChannelServer { public static void main(String[] args) throws IOException { SocketChannel server = SocketChannel.open(); SocketAddress socketAddr = new InetSocketAddress("localhost", 9000); server.connect(socketAddr); Path path = Paths.get("C:/Test/temp.txt"); FileChannel fileChannel = FileChannel.open(path); ByteBuffer buffer = ByteBuffer.allocate(1024); while(fileChannel.read(buffer) > 0) { buffer.flip(); server.write(buffer); buffer.clear(); } fileChannel.close(); System.out.println("File Sent"); server.close(); } }
输出
运行服务器将打印以下内容。
Connection Set: /127.0.0.1:49558 File Received
Java NIO - ServerSocket 通道
Java NIO 服务器套接字通道也是一个可选类型通道,用于连接套接字的面向流的数据流。服务器套接字通道可以通过调用其静态open()方法来创建,前提是任何预先存在的套接字尚未存在。服务器套接字通道是通过调用 open 方法创建但尚未绑定。为了绑定套接字通道,需要调用bind()方法。
这里需要提到的一点是,如果通道未绑定并且尝试进行任何 I/O 操作,则该通道会抛出 NotYetBoundException。因此,在执行任何 IO 操作之前必须确保通道已绑定。
通过调用 ServerSocketChannel.accept() 方法来侦听服务器套接字通道的传入连接。当accept()方法返回时,它返回一个带有传入连接的SocketChannel。因此,accept() 方法会阻塞,直到传入连接到达。如果通道处于非阻塞模式,则如果没有挂起的连接,accept 方法将立即返回 null。否则它将无限期地阻塞,直到有新的连接可用或发生 I/O 错误。
新通道的套接字最初是未绑定的;在接受连接之前,它必须通过其套接字的绑定方法之一绑定到特定地址。此外,新通道是通过调用系统范围默认 SelectorProvider 对象的 openServerSocketChannel 方法创建的。
就像套接字通道一样,服务器套接字通道可以使用read()方法读取数据。首先分配缓冲区。从 ServerSocketChannel 读取的数据存储到缓冲区中。其次,我们调用 ServerSocketChannel.read() 方法,它将从 ServerSocketChannel 读取数据到缓冲区中。read() 方法的整数值返回写入缓冲区的字节数
类似地,可以使用write()方法(使用 buffer 作为参数)将数据写入服务器套接字通道。通常在 while 循环中使用 write 方法,因为需要重复 write() 方法,直到 Buffer 没有更多字节可供写入。
Socket通道的重要方法
bind(SocketAddress local) - 此方法用于将套接字通道绑定到本地地址,该地址作为此方法的参数提供。
Accept() - 此方法用于接受与此通道的套接字建立的连接。
connect(SocketAddress remote) - 此方法用于将套接字连接到远程地址。
finishConnect() - 此方法用于完成连接套接字通道的过程。
getRemoteAddress() - 此方法返回通道套接字连接的远程位置的地址。
isConnected() - 正如已经提到的,此方法返回套接字通道的连接状态,即是否已连接。
open() - Open 方法用于打开没有指定地址的套接字通道。这种便捷方法的工作方式就像调用 open() 方法,在生成的服务器套接字通道上调用 connect 方法,将其传递到远程,然后返回该通道渠道。
read(ByteBuffer dst) - 此方法用于通过套接字通道从给定缓冲区读取数据。
setOption(SocketOption<T> name, T value) - 此方法设置套接字选项的值。
socket() - 此方法检索与此通道关联的服务器套接字。
validOps() - 此方法返回一个操作集,标识此通道支持的操作。服务器套接字通道仅支持接受新连接,因此此方法返回 SelectionKey.OP_ACCEPT。
例子
以下示例展示了如何从 Java NIO ServerSocketChannel 发送数据。
C:/测试/temp.txt
Hello World!
客户端:SocketChannelClient.java
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.EnumSet; public class SocketChannelClient { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocket = null; SocketChannel client = null; serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); client = serverSocket.accept(); System.out.println("Connection Set: " + client.getRemoteAddress()); Path path = Paths.get("C:/Test/temp1.txt"); FileChannel fileChannel = FileChannel.open(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE) ); ByteBuffer buffer = ByteBuffer.allocate(1024); while(client.read(buffer) > 0) { buffer.flip(); fileChannel.write(buffer); buffer.clear(); } fileChannel.close(); System.out.println("File Received"); client.close(); } }
输出
在服务器启动之前,运行客户端不会打印任何内容。
服务器:SocketChannelServer.java
import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; import java.nio.file.Path; import java.nio.file.Paths; public class SocketChannelServer { public static void main(String[] args) throws IOException { SocketChannel server = SocketChannel.open(); SocketAddress socketAddr = new InetSocketAddress("localhost", 9000); server.connect(socketAddr); Path path = Paths.get("C:/Test/temp.txt"); FileChannel fileChannel = FileChannel.open(path); ByteBuffer buffer = ByteBuffer.allocate(1024); while(fileChannel.read(buffer) > 0) { buffer.flip(); server.write(buffer); buffer.clear(); } fileChannel.close(); System.out.println("File Sent"); server.close(); } }
输出
运行服务器将打印以下内容。
Connection Set: /127.0.0.1:49558 File Received
Java NIO - 分散
我们知道,与 Java 的传统 IO API 相比,Java NIO 是一种针对数据 IO 操作更加优化的 API。Java NIO 提供的另一个额外支持是从通道的多个缓冲区读取/写入数据。这种多次读取写支持称为分散和收集,其中在读取数据的情况下,数据从单个通道分散到多个缓冲区,而在写入数据的情况下,数据从多个缓冲区收集到单个通道。
为了实现从通道的多次读取和写入,Java NIO 提供了 ScatteringByteChannel 和 GatheringByteChannel API 用于读取和写入数据,如下例所示。
散射字节通道
从多个通道读取- 在此我们将数据从单个通道读取到多个缓冲区中。为此,分配多个缓冲区并将其添加到缓冲区类型数组中。然后该数组作为参数传递给 ScatteringByteChannel read() 方法,该方法然后按照缓冲区在数组中出现的顺序从通道写入数据。一旦缓冲区已满,通道就会继续填充下一个缓冲区。
以下示例展示了 Java NIO 中如何执行数据分散
C:/测试/temp.txt
Hello World!
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ScatteringByteChannel; public class ScatterExample { private static String FILENAME = "C:/Test/temp.txt"; public static void main(String[] args) { ByteBuffer bLen1 = ByteBuffer.allocate(1024); ByteBuffer bLen2 = ByteBuffer.allocate(1024); FileInputStream in; try { in = new FileInputStream(FILENAME); ScatteringByteChannel scatter = in.getChannel(); scatter.read(new ByteBuffer[] {bLen1, bLen2}); bLen1.position(0); bLen2.position(0); int len1 = bLen1.asIntBuffer().get(); int len2 = bLen2.asIntBuffer().get(); System.out.println("Scattering : Len1 = " + len1); System.out.println("Scattering : Len2 = " + len2); } catch (FileNotFoundException exObj) { exObj.printStackTrace(); } catch (IOException ioObj) { ioObj.printStackTrace(); } } }
输出
Scattering : Len1 = 1214606444 Scattering : Len2 = 0
最后可以得出结论,Java NIO 中的分散/聚集方法在正确使用时是一种优化和多任务的方法。它允许您将将读取的数据分离到多个存储桶中或组装的繁重工作委托给操作系统毫无疑问,这可以通过避免缓冲区复制来节省时间并更有效地使用操作系统,并减少需要编写和调试的代码量。
Java NIO - 收集
我们知道,与 Java 的传统 IO API 相比,Java NIO 是一种针对数据 IO 操作更加优化的 API。Java NIO 提供的另一个额外支持是从通道的多个缓冲区读取/写入数据。这种多次读取写支持称为分散和收集,其中在读取数据的情况下,数据从单个通道分散到多个缓冲区,而在写入数据的情况下,数据从多个缓冲区收集到单个通道。
为了实现从通道的多次读取和写入,Java NIO 提供了 ScatteringByteChannel 和 GatheringByteChannel API 用于读取和写入数据,如下例所示。
GatheringByteChannel
写入多个通道- 在此,我们将多个缓冲区中的数据写入单个通道。为此,再次分配多个缓冲区并将其添加到缓冲区类型数组中。然后该数组作为参数传递给 GatheringByteChannel write() 方法然后按照缓冲区在数组中出现的顺序从多个缓冲区写入数据。这里要记住的一点是仅写入缓冲区位置和限制之间的数据。
以下示例展示了 Java NIO 中如何执行数据收集
import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.GatheringByteChannel; public class GatherExample { private static String FILENAME = "C:/Test/temp.txt"; public static void main(String[] args) { String stream1 = "Gather data stream first"; String stream2 = "Gather data stream second"; ByteBuffer bLen1 = ByteBuffer.allocate(1024); ByteBuffer bLen2 = ByteBuffer.allocate(1024); // Next two buffer hold the data we want to write ByteBuffer bstream1 = ByteBuffer.wrap(stream1.getBytes()); ByteBuffer bstream2 = ByteBuffer.wrap(stream2.getBytes()); int len1 = stream1.length(); int len2 = stream2.length(); // Writing length(data) to the Buffer bLen1.asIntBuffer().put(len1); bLen2.asIntBuffer().put(len2); System.out.println("Gathering : Len1 = " + len1); System.out.println("Gathering : Len2 = " + len2); // Write data to the file try { FileOutputStream out = new FileOutputStream(FILENAME); GatheringByteChannel gather = out.getChannel(); gather.write(new ByteBuffer[] {bLen1, bLen2, bstream1, bstream2}); out.close(); gather.close(); } catch (FileNotFoundException exObj) { exObj.printStackTrace(); } catch(IOException ioObj) { ioObj.printStackTrace(); } } }
输出
Gathering : Len1 = 24 Gathering : Len2 = 25
最后可以得出结论,Java NIO 中的分散/聚集方法在正确使用时是一种优化和多任务的方法。它允许您将将读取的数据分离到多个存储桶中或组装的繁重工作委托给操作系统毫无疑问,这可以通过避免缓冲区复制来节省时间并更有效地使用操作系统,并减少需要编写和调试的代码量。
Java NIO - 缓冲区
Java NIO 中的缓冲区可以被视为一个简单的对象,它充当固定大小的数据块容器,可用于将数据写入通道或从通道读取数据,以便缓冲区充当通道的端点。
它提供了一组方法,可以更方便地处理内存块,以便从通道读取数据和向通道写入数据。
与经典 IO 相比,缓冲区使 NIO 包更高效、更快,因为 IO 数据以流的形式处理,不支持异步和并发数据流。此外 IO 不允许以块或字节组执行数据。
定义 Java NIO 缓冲区的主要参数可以定义为 -
容量- 可以存储在缓冲区中的最大数据/字节量。缓冲区的容量无法更改。一旦缓冲区已满,应在写入之前将其清除。
Limit - Limit 的含义取决于 Buffer 的模式,即在 Buffer Limit 的写入模式下等于容量,这意味着可以在 buffer 中写入的最大数据。而在 buffer 的读取模式下 Limit 意味着可以写入的数据量的限制从Buffer中读取。
Position - 指向缓冲区中光标的当前位置。在创建缓冲区时最初设置为 0,换句话说,它是要读取或写入的下一个元素的索引,通过 get() 和 put 自动更新() 方法。
Mark - 标记缓冲区中位置的书签。当调用 mark() 方法时,将记录当前位置,当调用 reset() 时,将恢复标记的位置。
缓冲器类型
Java NIO 缓冲区可以根据缓冲区处理的数据类型分为以下变体 -
- 字节缓冲区
- 映射字节缓冲区
- 字符缓冲区
- 双缓冲
- 浮动缓冲区
- 内部缓冲区
- 长缓冲区
- 短缓冲区
Buffer的重要方法
正如已经提到的,Buffer 作为内存对象,它提供了一系列方法,可以更方便地处理内存块。以下是 Buffer 的重要方法 -
allocate(intcapacity) - 此方法用于分配一个以容量为参数的新缓冲区。如果传递的容量为负整数,则分配方法会抛出 IllegalArgumentException。
read() 和 put() - 通道的 read 方法用于将数据从通道写入缓冲区,而 put 是缓冲区的一种方法,用于将数据写入缓冲区。
Flip() - Flip 方法将 Buffer 的模式从写入模式切换为读取模式。它还将位置设置回 0,并将限制设置为写入时位置的位置。
write() 和 get() - 通道的 write 方法用于将数据从缓冲区写入通道,而 get 是缓冲区的方法,用于从缓冲区读取数据。
rewind() - 当需要重读时使用 rewind 方法,因为它将位置设置回零并且不改变 limit 的值。
clear()和compact() -clear和compact这两种方法都用于使缓冲区从读取模式变为写入模式。clear()方法使位置为零,限制等于容量,在该方法中,缓冲区中的数据不会被清除,只是标记会被重新初始化。
另一方面,当仍然有一些未读数据并且我们仍然使用缓冲区的写入模式时,使用compact()方法,在这种情况下,compact方法将所有未读数据复制到缓冲区的开头,并将位置设置为最后一个未读元素之后.limit 属性仍然设置为capacity。
mark() 和 reset() - 顾名思义,mark 方法用于标记缓冲区中的任何特定位置,而重置则使位置回到标记位置。
例子
以下示例显示了上述定义方法的实现。
import java.nio.ByteBuffer; import java.nio.CharBuffer; public class BufferDemo { public static void main (String [] args) { //allocate a character type buffer. CharBuffer buffer = CharBuffer.allocate(10); String text = "bufferDemo"; System.out.println("Input text: " + text); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); //put character in buffer. buffer.put(c); } int buffPos = buffer.position(); System.out.println("Position after data is written into buffer: " + buffPos); buffer.flip(); System.out.println("Reading buffer contents:"); while (buffer.hasRemaining()) { System.out.println(buffer.get()); } //set the position of buffer to 5. buffer.position(5); //sets this buffer's mark at its position buffer.mark(); //try to change the position buffer.position(6); //calling reset method to restore to the position we marked. //reset() raise InvalidMarkException if either the new position is less //than the position marked or merk has not been setted. buffer.reset(); System.out.println("Restored buffer position : " + buffer.position()); } }
输出
Input text: bufferDemo Position after data is written into buffer: 10 Reading buffer contents: b u f f e r D e m o Restored buffer position : 5
Java NIO - 选择器
我们知道,Java NIO 支持来自和到达通道和缓冲区的多个事务。因此,为了检查一个或多个 NIO Channel,并确定哪些通道已准备好进行数据事务,即读取或写入 Java NIO 提供选择器。
使用选择器,我们可以创建一个线程来知道哪个通道已准备好进行数据写入和读取,并且可以处理该特定通道。
我们可以通过调用其静态方法open()来获取选择器实例。打开选择器后,我们必须向其注册一个非阻塞模式通道,该通道返回 SelectionKey 的实例。
SelectionKey 基本上是可以通过通道执行的操作的集合,或者我们可以说我们可以借助选择键了解通道的状态。
选择键代表的主要操作或通道状态是 -
SelectionKey.OP_CONNECT - 准备连接到服务器的通道。
SelectionKey.OP_ACCEPT - 准备接受传入连接的通道。
SelectionKey.OP_READ - 准备读取数据的通道。
SelectionKey.OP_WRITE - 准备好数据写入的通道。
注册后获得的选择密钥有一些重要的方法,如下所述 -
Attach() - 此方法用于使用密钥附加对象。将对象附加到通道的主要目的是识别相同的通道。
Attachment() - 此方法用于保留通道中附加的对象。
channel() - 此方法用于获取为其创建特定密钥的通道。
Selector() - 此方法用于获取为其创建特定键的选择器。
isValid() - 此方法返回密钥是否有效的天气。
isReadable() - 此方法表明天气键的通道是否已准备好读取。
isWritable() - 此方法表明天气键的通道是否已准备好写入。
isAcceptable() - 此方法表明天气密钥的通道是否已准备好接受传入连接。
isConnectable() - 此方法测试此键的通道是否已完成或未能完成其套接字连接操作。
isAcceptable() - 此方法测试此密钥的通道是否准备好接受新的套接字连接。
interestOps() - 此方法检索此键的兴趣集。
ReadyOps() - 此方法检索就绪集,这是通道已准备好的操作集。
我们可以通过调用选择器的静态方法select()从选择器中选择一个通道。选择器的 Select 方法被重载为 -
select() - 此方法会阻塞当前线程,直到至少一个通道准备好接收其注册的事件。
select(long timeout) - 此方法与 select() 相同,只是它会阻塞线程最多超时毫秒(参数)。
selectNow() - 此方法根本不会阻塞。无论通道准备就绪,它都会立即返回。
另外,为了留下调用 select 方法的阻塞线程,可以从选择器实例调用wakeup()方法,之后在 select() 内等待的线程将立即返回。
最后,我们可以通过调用close()方法来关闭选择器,该方法在关闭选择器的同时也会使所有在此选择器中注册的 SelectionKey 实例失效。
例子
import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class SelectorDemo { public static void main(String[] args) throws IOException { String demo_text = "This is a demo String"; Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress("localhost", 5454)); serverSocket.configureBlocking(false); serverSocket.register(selector, SelectionKey.OP_ACCEPT); ByteBuffer buffer = ByteBuffer.allocate(256); while (true) { selector.select(); Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectedKeys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); int interestOps = key.interestOps(); System.out.println(interestOps); if (key.isAcceptable()) { SocketChannel client = serverSocket.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); client.read(buffer); if (new String(buffer.array()).trim().equals(demo_text)) { client.close(); System.out.println("Not accepting client messages anymore"); } buffer.flip(); client.write(buffer); buffer.clear(); } iter.remove(); } } } }
Java NIO - 管道
在Java NIO中,管道是一个用于在两个线程之间写入和读取数据的组件。管道主要由两个负责数据传播的通道组成。
两个组成通道中,一个称为Sink通道,主要用于写入数据,另一个称为Source通道,主要用于从Sink通道读取数据。
在数据写入和读取过程中,数据同步保持有序,因为必须确保数据的读取顺序与写入 Pipe 的顺序相同。
需要注意的是,Pipe 中的数据是单向流动的,即数据只能写入 Sink 通道,并且只能从 Source 通道读取。
在Java中,NIO管道被定义为一个抽象类,主要有三个方法,其中两个是抽象的。
Pipe类的方法
open() - 此方法用于获取 Pipe 的实例,或者我们可以说管道是通过调用此方法创建的。
sink() - 此方法返回 Pipe 的接收通道,该通道用于通过调用其 write 方法来写入数据。
source() - 此方法返回 Pipe 的源通道,该通道用于通过调用其 read 方法来读取数据。
例子
下面的例子展示了Java NIO管道的实现。
import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Pipe; public class PipeDemo { public static void main(String[] args) throws IOException { //An instance of Pipe is created Pipe pipe = Pipe.open(); // gets the pipe's sink channel Pipe.SinkChannel skChannel = pipe.sink(); String testData = "Test Data to Check java NIO Channels Pipe."; ByteBuffer buffer = ByteBuffer.allocate(512); buffer.clear(); buffer.put(testData.getBytes()); buffer.flip(); //write data into sink channel. while(buffer.hasRemaining()) { skChannel.write(buffer); } //gets pipe's source channel Pipe.SourceChannel sourceChannel = pipe.source(); buffer = ByteBuffer.allocate(512); //write data into console while(sourceChannel.read(buffer) > 0){ //limit is set to current position and position is set to zero buffer.flip(); while(buffer.hasRemaining()){ char ch = (char) buffer.get(); System.out.print(ch); } //position is set to zero and limit is set to capacity to clear the buffer. buffer.clear(); } } }
输出
Test Data to Check java NIO Channels Pipe.
假设我们有一个文本文件c:/test.txt,其中包含以下内容。该文件将用作我们示例程序的输入。
Java NIO - 路径
顾名思义,路径是文件系统中文件或目录等实体的特定位置,以便人们可以在该特定位置搜索和访问它。
从技术上来说,Path 是 Java 7 版本中 Java NIO 文件包中引入的一个接口,是特定文件系统中位置的表示。由于 Path 接口位于 Java NIO 包中,因此它的限定名称为 java .nio.文件.路径。
一般来说,实体的路径可以有两种类型,一种是绝对路径,另一种是相对路径。从这两种路径的名称来看,绝对路径是从根到其所在实体的位置地址,而相对路径是位置地址这是相对于其他路径的。路径在其定义中使用分隔符,对于 Windows 为“\”,对于 unix 操作系统为“/”。
为了获取 Path 的实例,我们可以使用 java.nio.file.Paths 类get()的静态方法。该方法将路径字符串或连接形成路径字符串的字符串序列转换为 Path 实例如果传递的参数包含非法字符,此方法还会抛出运行时 InvalidPathException。
如上所述,绝对路径是通过传递根元素和定位文件所需的完整目录列表来检索的。而相对路径可以通过将基本路径与相对路径组合来检索。两个路径的检索将在以下示例中说明
例子
package com.java.nio; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.file.FileSystem; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; public class PathDemo { public static void main(String[] args) throws IOException { Path relative = Paths.get("file2.txt"); System.out.println("Relative path: " + relative); Path absolute = relative.toAbsolutePath(); System.out.println("Absolute path: " + absolute); } }
到目前为止我们知道了什么是Path接口,为什么我们需要它以及我们如何访问它。现在我们知道Path接口为我们提供了哪些重要的方法。
Path接口的重要方法
getFileName() - 返回创建此对象的文件系统。
getName() - 返回此路径的名称元素作为 Path 对象。
getNameCount() - 返回路径中名称元素的数量。
subpath() - 返回一个相对路径,它是该路径的名称元素的子序列。
getParent() - 返回父路径,如果该路径没有父路径,则返回 null。
getRoot() - 返回此路径的根组件作为 Path 对象,如果此路径没有根组件,则返回 null。
toAbsolutePath() - 返回表示该路径的绝对路径的 Path 对象。
toRealPath() - 返回现有文件的真实路径。
toFile() - 返回表示此路径的 File 对象。
Normalize() - 返回一个路径,该路径是消除了冗余名称元素的路径。
CompareTo(Path other) - 按字典顺序比较两个抽象路径。如果参数等于此路径,则此方法返回零;如果此路径按字典顺序小于参数,则返回小于零的值;如果此路径是大于零的值按字典顺序大于参数。
endsWith(Path other) - 测试此路径是否以给定路径结束。如果给定路径有 N 个元素,并且没有根组件,并且此路径有 N 个或更多元素,则此路径以给定路径结束,如果最后 N从距根最远的元素开始,每条路径的元素都是相等的。
endsWith(String other) - 测试此路径是否以 Path 结尾,该 Path 是通过转换给定路径字符串构造的,完全按照endsWith(Path) 方法指定的方式。
例子
以下示例说明了上面提到的 Path 接口的不同方法 -
package com.java.nio; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.file.FileSystem; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; public class PathDemo { public static void main(String[] args) throws IOException { Path path = Paths.get("D:/workspace/ContentW/Saurav_CV.docx"); FileSystem fs = path.getFileSystem(); System.out.println(fs.toString()); System.out.println(path.isAbsolute()); System.out.println(path.getFileName()); System.out.println(path.toAbsolutePath().toString()); System.out.println(path.getRoot()); System.out.println(path.getParent()); System.out.println(path.getNameCount()); System.out.println(path.getName(0)); System.out.println(path.subpath(0, 2)); System.out.println(path.toString()); System.out.println(path.getNameCount()); Path realPath = path.toRealPath(LinkOption.NOFOLLOW_LINKS); System.out.println(realPath.toString()); String originalPath = "d:\\data\\projects\\a-project\\..\\another-project"; Path path1 = Paths.get(originalPath); Path path2 = path1.normalize(); System.out.println("path2 = " + path2); } }
Java NIO - 文件
Java NIO 包提供了另一种名为 Files 的实用 API,它基本上用于使用其静态方法(主要适用于 Path 对象)来操作文件和目录。
正如 Path 教程中提到的,在 Java 7 版本的 File 包中,Java NIO 包中引入了 Path 接口。因此本教程适用于同一个 File 包。
此类仅由对文件、目录或其他类型的文件进行操作的静态方法组成。在大多数情况下,此处定义的方法将委托给关联的文件系统提供程序来执行文件操作。
Files 类中定义了许多方法,也可以从 Java 文档中读取这些方法。在本教程中,我们尝试介绍 Java NIO Files 类的所有方法中的一些重要方法。
Files类的重要方法。
以下是 Java NIO Files 类中定义的重要方法。
createFile(Path filePath, FileAttribute attrs) - Files 类提供此方法来使用指定的 Path 创建文件。
例子
package com.java.nio; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class CreateFile { public static void main(String[] args) { //initialize Path object Path path = Paths.get("D:file.txt"); //create file try { Path createdFilePath = Files.createFile(path); System.out.println("Created a file at : "+createdFilePath); } catch (IOException e) { e.printStackTrace(); } } }
输出
Created a file at : D:\data\file.txt
copy(InputStream in, Path target, CopyOption… options) - 此方法用于将指定输入流中的所有字节复制到指定目标文件,并返回以长值形式读取或写入的字节数。此参数的 LinkOption 具有以下值 -
COPY_ATTRIBUTES - 将属性复制到新文件,例如上次修改时间属性。
REPLACE_EXISTING - 替换现有文件(如果存在)。
NOFOLLOW_LINKS - 如果文件是符号链接,则复制链接本身,而不是链接的目标。
例子
package com.java.nio; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.List; public class WriteFile { public static void main(String[] args) { Path sourceFile = Paths.get("D:file.txt"); Path targetFile = Paths.get("D:fileCopy.txt"); try { Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { System.err.format("I/O Error when copying file"); } Path wiki_path = Paths.get("D:fileCopy.txt"); Charset charset = Charset.forName("ISO-8859-1"); try { List<String> lines = Files.readAllLines(wiki_path, charset); for (String line : lines) { System.out.println(line); } } catch (IOException e) { System.out.println(e); } } }
输出
To be or not to be?
createDirectories(Path dir, FileAttribute<?>...attrs) - 此方法用于通过创建所有不存在的父目录来使用给定路径创建目录。
delete(Path path) - 此方法用于从指定路径删除文件。如果指定路径中不存在该文件,或者该文件是目录且可能不为空且无法删除,则抛出 NoSuchFileException。
contains(Path path) - 此方法用于检查指定路径中是否存在文件,如果文件存在,则返回 true,否则返回 false。
readAllBytes(Path path) - 此方法用于从给定路径的文件中读取所有字节,并返回包含从文件中读取的字节的字节数组。
例子
package com.java.nio; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; public class ReadFile { public static void main(String[] args) { Path wiki_path = Paths.get("D:file.txt"); Charset charset = Charset.forName("ISO-8859-1"); try { List<String> lines = Files.readAllLines(wiki_path, charset); for (String line : lines) { System.out.println(line); } } catch (IOException e) { System.out.println(e); } } }
输出
Welcome to file.
size(Path path) - 此方法用于获取指定路径处的文件大小(以字节为单位)。
write(Path path, byte[] bytes, OpenOption... options) - 此方法用于将字节写入指定路径的文件。
例子
package com.java.nio; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; public class WriteFile { public static void main(String[] args) { Path path = Paths.get("D:file.txt"); String question = "To be or not to be?"; Charset charset = Charset.forName("ISO-8859-1"); try { Files.write(path, question.getBytes()); List<String> lines = Files.readAllLines(path, charset); for (String line : lines) { System.out.println(line); } } catch (IOException e) { System.out.println(e); } } }
输出
To be or not to be?
Java NIO - AsynchronousFileChannel
我们知道Java NIO支持并发和多线程,这允许我们同时处理不同的通道。因此Java NIO包中负责此操作的API是AsynchronousFileChannel,它定义在NIO通道包下。因此限定名称对于 AsynchronousFileChannel 是java.nio.channels.AsynchronousFileChannel。
AsynchronousFileChannel 与 NIO 的 FileChannel 类似,不同之处在于该通道使文件操作能够异步执行,这与同步 I/O 操作不同,同步 I/O 操作中线程进入操作并等待请求完成。因此,异步通道可以安全使用由多个并发线程。
在异步中,请求由线程传递到操作系统内核以完成它,同时线程继续处理另一个作业。一旦内核的作业完成,它会向线程发出信号,然后线程确认该信号并中断当前作业并处理该作业。根据需要进行 I/O 作业。
为了实现并发,该通道提供了两种方法,一种是返回java.util.concurrent.Future 对象,另一种是将java.nio.channels.CompletionHandler类型的对象传递给操作。
我们将通过示例一一理解这两种方法。
Future 对象- 在此,Future 接口的实例从通道返回。在 Future 接口中,有