首先了解关于zero-copy相关的知识点
在上一篇文章中简单介绍了zero-copy的相关知识,提到了mmap内存直接映射方式,这种方式介于sendfile系统调用与传统IO之间,其中一个重要原因是sendfile完全在内核空间中完成的,这对于应用程序来说就无法对数据进行操作,也由此,javaNIO是基于mmap内存映射的方式来实现零拷贝的。
Java NIO引入了用于通道的缓冲区的ByteBuffer,ByteBuffer有三个主要的实现:
Java NIO引入了用于通道的缓冲区的ByteBuffer。 ByteBuffer有三个主要的实现:
HeapByteBuffer
在调用ByteBuffer.allocate()时使用。 它被称为堆,因为它保存在JVM的堆空间中,因此您可以获得所有优势,如GC支持和缓存优化。 但是,它不是页面对齐的,这意味着如果您需要通过JNI与本地代码交谈,JVM将不得不复制到对齐的缓冲区空间。
DirectByteBuffer
在调用ByteBuffer.allocateDirect()时使用。 JVM将使用malloc()在堆空间之外分配内存空间。 因为它不是由JVM管理的,所以你的内存空间是页面对齐的,不受GC影响,这使得它成为处理本地代码的完美选择。 然而,你要C程序员一样,自己管理这个内存,必须自己分配和释放内存来防止内存泄漏。
MappedByteBuffer
在调用FileChannel.map()时使用。 与DirectByteBuffer类似,这也是JVM堆外部的情况。 它基本上作为OS mmap()系统调用的包装函数,以便代码直接操作映射的物理内存数据。
以上解释听起来还是云里雾里的,其实
NIO的直接内存是由MappedByteBuffer
实现的。核心即是map()
方法,该方法把文件映射到内存中,获得内存地址addr,然后通过这个addr构造MappedByteBuffer类,以暴露各种文件操作API。
由于MappedByteBuffer申请的是堆外内存,因此不受Minor GC控制,只能在发生Full GC时才能被回收。而DirectByteBuffer
改善了这一情况,它是MappedByteBuffer类的子类,同时它实现了DirectBuffer接口,维护一个Cleaner对象来完成内存回收。因此它既可以通过Full GC来回收内存,也可以调用clean()
方法来进行回收。
对于HeapByteBuffer与MappedByteBuffer的创建方式如下
allocate 方法(创建堆缓冲区)
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
allocate方法创建的缓冲区是创建的HeapByteBuffer实例。
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
}
从堆缓冲区中看出,所谓堆缓冲区就是在堆内存中创建一个byte[]数组。
allocateDirect创建直接缓冲区
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
我们发现allocate方法创建的缓冲区是创建的DirectByteBuffer实例。
直接缓冲区是通过java中Unsafe类进行在物理内存中创建缓冲区。
与Channel的配合使用
package com.expgiga.NIO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
/**
* 一、Channel:用于源节点与目标节点之间的连接。在Java NIO中,负责缓冲区中数据传输,Channel本身不存储数据,因此需要配合缓冲区进行传输。
*
* 二、Channel的实现类:
* java.nio.channels.Channel 接口:
* |-- FileChannel
* |-- SocketChannel
* |-- ServerSocketChannel
* |-- DatagramChannel
*
* 三、获取通道Channel
* 1.Java针对支持通道的类提供了getChannel()方法
* 本地IO
* FileInputStream/FileOutputStream
* RandomAccessFile
*
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
*
* 2.在jdk1.7中的NIO.2针对各个通道提供了静态方法open()
*
* 3.在jdk1.7中的NIO.2的Files工具类的newByteChannel()
*
* 四、通道之间的数据传输
* transferFrom()
* transferTo()
*
*/
public class TestChannel {
public static void main(String[] args) throws IOException {
/*
* 1.利用通道完成文件的复制(非直接缓冲区)
*/
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
//1.获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3.将通道中的数据缓冲区中
while (inChannel.read(buffer) != -1) {
buffer.flip();//切换成都数据模式
//4.将缓冲区中的数据写入通道中
outChannel.write(buffer);
buffer.clear();//清空缓冲区
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* 2.利用(直接缓冲区)通道完成文件的复制(内存映射文件的方式)
*/
long start = System.currentTimeMillis();
FileChannel inChannel2 = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel2 = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel2.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel2.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel2.close();
outChannel2.close();
long end = System.currentTimeMillis();
System.out.println("耗费的时间为:" + (end - start));
/*
* 通道之间的数据传输(直接缓冲区)
*/
FileChannel inChannel3 = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel3 = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
inChannel3.transferTo(0, inChannel3.size(), outChannel3);
//等价于
// outChannel3.transferFrom(inChannel3, 0, inChannel3.size());
inChannel3.close();
outChannel3.close();
}
}