java nio是如何实现零拷贝(zero-copy)的

首先了解关于zero-copy相关的知识点

java  nio是如何实现zero-copy的

在上一篇文章中简单介绍了zero-copy的相关知识,提到了mmap内存直接映射方式,这种方式介于sendfile系统调用与传统IO之间,其中一个重要原因是sendfile完全在内核空间中完成的,这对于应用程序来说就无法对数据进行操作,也由此,javaNIO是基于mmap内存映射的方式来实现零拷贝的。

实现方式

Java NIO引入了用于通道的缓冲区的ByteBuffer,ByteBuffer有三个主要的实现:

NIO DirectByteBuffer

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();
    }
}

 

你可能感兴趣的:(IO)