Java NIO Channel
Java NIO(New IO)是 Java 的替代 IO API(来自 Java 1.4),意味着替代标准 Java IO 和 Java Networking API。 Java NIO 提供了与标准 IO API 不同的使用 IO 的方式。
概述
Java NIO 主要有以下三个核心组件:
- Channels
- Buffers
- Selectors
除此之外,Java NIO 还有许多类和组件,但是 NIO 的核心 API 主要还是由 channel
,buffer
和 selector
构成。其余的组件,如 Pipe
和 FileLock
,只是与这三个核心组件一起使用的实用程序类,本篇文章先来来介绍 Channel。
通常,NIO 中的所有 IO 都以 Channel 开始,Channel(通道)有点像 Stream(流),但是它又有一些区别:
- 你既可以从 Channle 中读取数据,也可以向其写入数据。而 Stream 通常只能单向操作,要么读,要么写;
- Channel 支持异步读取或写入数据;
- 数据从 Channel 中读取到 Buffer 中,从 Buffer 写入到 Channel 中。
示意图如下:
Channel 实现类
以下是 Java NIO 中最重要的 Channel 实现类:
- FileChannel:从文件中读取数据,或写入数据到文件中
- DatagramChannel:可以通过 UDP 协议在网络上读写数据。
- SocketChannel:可以通过 TCP 协议在网络上读写数据。
- ServerSocketChannel:监听传入的 TCP 连接,就像 Web 服务器一样。 对于每个传入连接,都会创建一个 SocketChannel。
本篇文章先来介绍 FileChannel,其余的后面再来介绍。
FileChannel
Java NIO FileChannel 是一个与文件相连接的通道。通过 FileChannel 你可以从文件中读取数据,以及写入数据到文件中。FileChannel 可以看作是标准 Java IO API 的替代方案。
FileChannel 只能以阻塞模式来使用,不能使用非阻塞模式。
Opening a FileChannel
在使用 FileChannel 之前,你必须先打开它。你不能直接打开一个 FileChannel,你需要通过 InputStream,OutputStream 或 RandomAccessFile 来获取 FileChannel,调用 getChannel()
。
代码示例:
1 | FileOutputStream outputStream = new FileOutputStream("file.txt"); |
Reading Data from a FileChannel
调用 read()
方法可以从 FileChannel 中读取相应的数据。例如:
1 | ByteBuffer buf = ByteBuffer.allocate(48); |
首先需要先分配一个 Buffer,然后调用 read () 方法 ,从 Channel 中读取数据到 Buffer 中。
返回的 int 数据,告诉我们有多少数据已被写入到了 buffer 中,如果返回 -1 ,则表示到达了 EOF (end-of-file)。
Writing Data to a FileChannel
写入数据需要调用 FileChannel.write () 方法,例如:
1 | String newData = "New String to wirte to file...." + System.currentTimeMillis(); |
由于没法保证有多少 bytes 将会被写入到 FileChannel 中,因此需要将 FileChannle.write () 方法在 while 循环中调用,指导 Buffer 中没有可写入的数据为止。
Closing a FileChannel
用完 FileChannel 之后,必须将其关闭。
1 | channel.close(); |
FileChannel Position
在读取或写入 FileChannel 时,你可以在特定位置执行此操作。
可以通过调用 position()
方法获取 FileChannel 对象的当前位置。也可以通过调用 position(long pos)
方法来设置 FileChannel 的位置。示例:
1 | long pos = fileChannel.position(); |
如果你在文件 EOF 之后设置 position,并尝试从 Channel 中读取数据,将会返回 -1
的结束标志。
如果你在文件 EOF 之后设置 position,并尝试写入数据到 Channel 中,文件将会扩展已符合设置的 position,并容纳新的字节。这可能会导致 “文件漏洞”,磁盘上的物理文件在写入数据中会存在间隙。
FileChannel Size
方法 size()
返回 channel 所连接的文件的字节大小,例如:
1 | long fileSize = channel.size(); |
FileChannel Truncate
通过 FileChannel.truncate()
方法,你可以截取文件,例如截取某个 FileChannel 1024 个字节
1 | channel.truncate(1024); |
FileChannel Force
FileChannel.force()
方法会将所有未写入的数据强制从 channel 刷到磁盘上。出于性能原因,操作系统可能会将数据缓存在内存中,因此在调用 force () 方法之前,无法保证写入 Channel 的数据是否写入到了磁盘中。
force()
有个 boolean 参数,表示是否需要刷新文件的 mete data(元数据)。
1 | channel.force(true) |
Channel to Channel Transfers
你可以使用 Java NIO 将数据从一个 Channel 直接转移到另一个 Channel 上。假如这个 Channel 是 FileChannel,那么你可以通过 transferTo () 和 transferFrom () 来实现。
transferFrom()
FileChannel.transferFrom () :将数据从源 Channel 转移到目标 Channel 中。代码如下:
1 | RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); |
position:指定数据传输的起始位置。
count:指定最大的传输的字节数。
transferTo()
1 | RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); |