欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java nio 新的四种文件传输 直接缓冲与非直接缓冲 速度对比

程序员文章站 2022-05-09 21:24:05
...

直接缓冲区与非直接缓冲区:

非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在JVM 内存
直接缓冲区:通过 allocateDirect() 方法或其他方法分配缓冲区,将缓冲区建立在物理内存中。可以提高效率;

ORACLE 官方对直接缓冲与非直接缓冲解释

直接与非直接缓冲区
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则Java 虚拟机会尽最大努力直接在此缓冲区上执行本机I/O 操作。也就是说,在每次调用基础操作系统的一个本机I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接字节缓冲区可以通过调用此类的allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接字节缓冲区还可以通过FileChannel 的map() 方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。Java 平台的实现有助于通过JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其isDirect()方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。

非直接缓冲区

java nio 新的四种文件传输 直接缓冲与非直接缓冲 速度对比

直接缓冲区

java nio 新的四种文件传输 直接缓冲与非直接缓冲 速度对比

温馨提示:byte数组最大 Integer.MAX_VALUE = 2147483647,所以缓冲区 buffer底层使用数组 数组存放最大字节数 不能超过2GB下面的test3() 方法将解释展示 若超过GB文件 多余文件将无法传输,另外byte’数组最大值 ,受到 JVM 运行环境影响 ,理论最大值为Integer.MAX_VALUE 实际上可能比理论值小.
若超出此虚拟机可分配最大数组, 则会抛出 java.lang.OutOfMemoryError: Java heap space;
若超过允许分配byte数组最大值则会抛出java.lang.OutOfMemoryError: Requested array size exceeds VM limit;

代码如下


import static org.junit.Assert.*;

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.channels.FileChannel.MapMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

/**
 * 本案例通过nio包对新方式对文件进行传输
 * 
 * @author xiaoqiang
 * @time 2017-7-3
 */
public class Demo {
    // 利用通道完成文件的复制(非直接缓冲区)
    // orac.rar 此文件大小约为2.1GB
    // 以下代码采用 JDK 1.7 try-with-resources

    @Test
    public void test1() {// 输出结果:19168 - 39153 - 18963
        long currentTime = System.currentTimeMillis();

        try (FileInputStream fis = new FileInputStream("E:\\装机包\\orac.rar");
                FileOutputStream fos = new FileOutputStream("F:\\1.rar");
                FileChannel inChannel = fis.getChannel();
                FileChannel outChannel = fos.getChannel();) {

            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            while (inChannel.read(buffer) != -1) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis() - currentTime);
    }

    // 使用直接缓冲区完成文件的复制(内存映射文件)
    @Test
    public void test2() {// 输出结果 1524 - 1430 - 13004
        // 此方法对受cpu状态影像较大
        long currentTime = System.currentTimeMillis();
        try (FileChannel inChannel = FileChannel.open(Paths.get("E:\\装机包\\orac.rar"), StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(Paths.get("F:\\1.rar"), StandardOpenOption.READ,
                        StandardOpenOption.WRITE, StandardOpenOption.CREATE);) {
            MappedByteBuffer inMappedBuffer = null;
            MappedByteBuffer outMappedBuffer = null;

            int BUFFER_SIZE = 1024 * 1024;
            byte[] dst = new byte[BUFFER_SIZE];
            long timer = (long) (inChannel.size() / BUFFER_SIZE);
            for (long i = 0; i < timer; i++) {
                inMappedBuffer = inChannel.map(MapMode.READ_ONLY, i * BUFFER_SIZE, BUFFER_SIZE);
                outMappedBuffer = outChannel.map(MapMode.READ_WRITE, i * BUFFER_SIZE, BUFFER_SIZE);
                inMappedBuffer.get(dst);
                outMappedBuffer.put(dst);
            }
            inMappedBuffer = inChannel.map(MapMode.READ_ONLY, BUFFER_SIZE * timer,
                    inChannel.size() - BUFFER_SIZE * timer);
            outMappedBuffer = outChannel.map(MapMode.READ_WRITE, BUFFER_SIZE * timer,
                    inChannel.size() - BUFFER_SIZE * timer);
            byte[] b = new byte[(int) (inChannel.size() - BUFFER_SIZE * timer)];
            inMappedBuffer.get(b);
            outMappedBuffer.put(b);

        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis() - currentTime);
    }

    // 通道之间的数据传输(直接缓冲区)
    @Test
    public void test3() { // 输出结果: 18845
        /**
         * 此方法只能适用于文件大小小于2GB文件 超过2GB剩余部分无法传输 底层进行类似test2 但是没有分隔,超过JVM可以容纳byte[]
         * 其余部分无法传输
         */
        long currentTime = System.currentTimeMillis();
        try (FileChannel inChannel = FileChannel.open(Paths.get("E:\\装机包\\orac.rar"), StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(Paths.get("F:\\1.rar"), StandardOpenOption.READ,
                        StandardOpenOption.WRITE, StandardOpenOption.CREATE);) {
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis() - currentTime);
    }

    // 底层使用输入输出流缓冲区进行传输
    @Test
    public void test4() {// 输出结果:46855
        long currentTime = System.currentTimeMillis();

        Path path1 = Paths.get("E:\\装机包\\orac.rar");
        Path path2 = Paths.get("F:\\1.rar");
        try {
            Files.copy(path1, path2, StandardCopyOption.COPY_ATTRIBUTES);// 复制新的属性到这文件
                                                                            // 原文件不删除

            // Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);//
            // 替代原文件

        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis() - currentTime);
    }
}