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,bufferselector 构成。其余的组件,如PipeFileLock,只是与这三个核心组件一起使用的实用程序类,本篇文章先来来介绍Channel。

通常,NIO中的所有IO都以Channel开始,Channel(通道)有点像Stream(流),但是它又有一些区别:

  • 你既可以从Channle中读取数据,也可以向其写入数据。而Stream通常只能单向操作,要么读,要么写;
  • Channel支持异步读取或写入数据;
  • 数据从Channel中读取到Buffer中,从Buffer写入到Channel中。

示意图如下:

channels-buffers

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
2
3
4
5
6
7
8
FileOutputStream outputStream = new FileOutputStream("file.txt");
FileChannel fileChannel = outputStream.getChannel();

FileInputStream inputStream = new FileInputStream("file.txt");
FileChannel fileChannel = inputStream.getChannel();

RandomAccessFile accessFile = new RandomAccessFile("file.txt", "rw");
FileChannel fileChannel = accessFile.getChannel();

Reading Data from a FileChannel

调用 read() 方法可以从FileChannel中读取相应的数据。例如:

1
2
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = fileChannel.read(buf);

首先需要先分配一个Buffer,然后调用 read() 方法 ,从Channel中读取数据到Buffer中。

返回的 int 数据,告诉我们有多少数据已被写入到了 buffer 中,如果返回 -1 ,则表示到达了EOF (end-of-file)。

Writing Data to a FileChannel

写入数据需要调用 FileChannel.write() 方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
String newData = "New String to wirte to file...." + System.currentTimeMillis();

ByteBuffer buffer = ByteBuffer.allocate(48);
buffer.clear();
buffer.put(newData.getBytes());
buffer.flip();

RandomAccessFile accessFile = new RandomAccessFile("file.txt", "rw");
FileChannel fileChannel = accessFile.getChannel();
while (buffer.hasRemaining()) {
fileChannel.write(buffer);
}

由于没法保证有多少bytes将会被写入到FileChannel中,因此需要将FileChannle.write() 方法在while循环中调用,指导Buffer中没有可写入的数据为止。

Closing a FileChannel

用完FileChannel之后,必须将其关闭。

1
channel.close();

FileChannel Position

在读取或写入FileChannel时,你可以在特定位置执行此操作。

可以通过调用 position() 方法获取FileChannel对象的当前位置。也可以通过调用 position(long pos) 方法来设置FileChannel的位置。示例:

1
2
3
4
long pos = fileChannel.position();

long newPos = 12;
fileChannel.position(newPos);

如果你在文件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() 来实现。

channel-transfer

transferFrom()

FileChannel.transferFrom() :将数据从源Channel转移到目标Channel中。代码如下:

1
2
3
4
5
6
7
8
9
10
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();

long position = 0;
long count = fromChannel.size();

toChannel.transferFrom(fromChannel, position, count);

position:指定数据传输的起始位置。

count:指定最大的传输的字节数。

transferTo()

1
2
3
4
5
6
7
8
9
10
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();

long position = 0;
long count = fromChannel.size();

fromChannel.transferTo(position,count,toChannel);

参考资料

请我喝杯咖啡吧~