Java NIO 文件IO

通道间的数据传输

这里主要介绍两个通道与通道之间数据传输的方式:

transferTo():把源通道的数据传输到目的通道中。

public static void main(String[] args) throws Exception {
//获取文件输入流
File file = new File("1.txt");
FileInputStream inputStream = new FileInputStream(file);
//从文件输入流获取通道
FileChannel inputStreamChannel = inputStream.getChannel();
//获取文件输出流
FileOutputStream outputStream = new FileOutputStream(new File("2.txt"));
//从文件输出流获取通道
FileChannel outputStreamChannel = outputStream.getChannel();
//创建一个byteBuffer,小文件所以就直接一次读取,不分多次循环了
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//把输入流通道的数据读取到输出流的通道
inputStreamChannel.transferTo(0, byteBuffer.limit(), outputStreamChannel);
//关闭通道
outputStream.close();
inputStream.close();
outputStreamChannel.close();
inputStreamChannel.close();
}

transferFrom():把来自源通道的数据传输到目的通道。

public static void main(String[] args) throws Exception {
//获取文件输入流
File file = new File("1.txt");
FileInputStream inputStream = new FileInputStream(file);
//从文件输入流获取通道
FileChannel inputStreamChannel = inputStream.getChannel();
//获取文件输出流
FileOutputStream outputStream = new FileOutputStream(new File("2.txt"));
//从文件输出流获取通道
FileChannel outputStreamChannel = outputStream.getChannel();
//创建一个byteBuffer,小文件所以就直接一次读取,不分多次循环了
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//把输入流通道的数据读取到输出流的通道
outputStreamChannel.transferFrom(inputStreamChannel,0,byteBuffer.limit());
//关闭通道
outputStream.close();
inputStream.close();
outputStreamChannel.close();
inputStreamChannel.close();
}

分散读取和聚合写入

我们先看一下FileChannel的源码:

public abstract class FileChannel extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel {
}

从源码中可以看出实现了GatheringByteChannel, ScatteringByteChannel接口。也就是支持分散读取和聚合写入的操作。怎么使用呢,请看以下例子:

我们写一个main方法来实现复制1.txt文件,文件内容是:

abcdefghijklmnopqrstuvwxyz//26个字母

代码如下:

public static void main(String[] args) throws Exception {
//获取文件输入流
File file = new File("1.txt");
FileInputStream inputStream = new FileInputStream(file);
//从文件输入流获取通道
FileChannel inputStreamChannel = inputStream.getChannel();
//获取文件输出流
FileOutputStream outputStream = new FileOutputStream(new File("2.txt"));
//从文件输出流获取通道
FileChannel outputStreamChannel = outputStream.getChannel();
//创建三个缓冲区,分别都是5
ByteBuffer byteBuffer1 = ByteBuffer.allocate(5);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(5);
ByteBuffer byteBuffer3 = ByteBuffer.allocate(5);
//创建一个缓冲区数组
ByteBuffer[] buffers = new ByteBuffer[]{byteBuffer1, byteBuffer2, byteBuffer3};
//循环写入到buffers缓冲区数组中,分散读取
long read;
long sumLength = 0;
while ((read = inputStreamChannel.read(buffers)) != -1) {
sumLength += read;
Arrays.stream(buffers)
.map(buffer -> "posstion=" + buffer.position() + ",limit=" + buffer.limit())
.forEach(System.out::println);
//切换模式
Arrays.stream(buffers).forEach(Buffer::flip);
//聚合写入到文件输出通道
outputStreamChannel.write(buffers);
//清空缓冲区
Arrays.stream(buffers).forEach(Buffer::clear);
}
System.out.println("总长度:" + sumLength);
//关闭通道
outputStream.close();
inputStream.close();
outputStreamChannel.close();
inputStreamChannel.close();
}

打印结果:

posstion=5,limit=5
posstion=5,limit=5
posstion=5,limit=5

posstion=5,limit=5
posstion=5,limit=5
posstion=1,limit=5

总长度:26

可以看到循环了两次。第一次循环时,三个缓冲区都读取了5个字节,总共读取了15,也就是读满了。还剩下11个字节,于是第二次循环时,前两个缓冲区分配了5个字节,最后一个缓冲区给他分配了1个字节,刚好读完。总共就是26个字节。

这就是分散读取,聚合写入的过程。

使用场景就是可以使用一个缓冲区数组,自动地根据需要去分配缓冲区的大小。可以减少内存消耗。网络IO也可以使用,这里就不写例子演示了。

非直接/直接缓冲区

非直接缓冲区的创建方式:

static ByteBuffer allocate(int capacity)

直接缓冲区的创建方式:

static ByteBuffer allocateDirect(int capacity)

非直接/直接缓冲区的区别示意图:

从示意图中我们可以发现,最大的不同在于直接缓冲区不需要再把文件内容copy到物理内存中。这就大大地提高了性能。其实在介绍Buffer时,我们就有接触到这个概念。直接缓冲区是堆外内存,在本地文件IO效率会更高一点。

接下来我们来对比一下效率,以一个136 MB的视频文件为例:

public static void main(String[] args) throws Exception {
long starTime = System.currentTimeMillis();
//获取文件输入流
File file = new File("D:\\小电影.mp4");//文件大小136 MB
FileInputStream inputStream = new FileInputStream(file);
//从文件输入流获取通道
FileChannel inputStreamChannel = inputStream.getChannel();
//获取文件输出流
FileOutputStream outputStream = new FileOutputStream(new File("D:\\test.mp4"));
//从文件输出流获取通道
FileChannel outputStreamChannel = outputStream.getChannel();
//创建一个直接缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(5 * 1024 * 1024);
//创建一个非直接缓冲区
//ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 1024 * 1024);
//写入到缓冲区
while (inputStreamChannel.read(byteBuffer) != -1) {
//切换读模式
byteBuffer.flip();
outputStreamChannel.write(byteBuffer);
byteBuffer.clear();
}
//关闭通道
outputStream.close();
inputStream.close();
outputStreamChannel.close();
inputStreamChannel.close();
long endTime = System.currentTimeMillis();
System.out.println("消耗时间:" + (endTime - starTime) + "毫秒");
}

结果:

直接缓冲区的消耗时间:283毫秒

非直接缓冲区的消耗时间:487毫秒

Author: Tunan
Link: http://yerias.github.io/2021/09/08/java/38/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.