NIO——transferFrom/transferTo与MappedByteBuffer问题/实验

一、实验(这个实验在 CSDN和开源中国上我都描述了具体流程,文章禁止转载)

实验准备: 需要进行文件复制的文件, 我的文件大小是 1.12 G
实验代码:

 @Test 
    public void test_4() throws Exception{
     
        File file = new File("F:\\temporary\\readFile\\测试文件.rar");
        File file2 = new File("F:\\temporary\\writeFile\\测试文件.rar");
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(file2);
        Long startTime = System.currentTimeMillis();
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
     
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();
            outChannel.transferFrom(inChannel, 0, inChannel.size()); 

            Long endTime = System.currentTimeMillis();
            System.out.println("transferFrom 1G 文件耗时: "+(endTime - startTime));
        }catch (Exception e) {
     
            e.printStackTrace();
        }finally {
           
            inChannel.close();
            outChannel.close();
        }
    }
     @Test
    public void test_5() throws Exception{
     
        File file = new File("F:\\temporary\\readFile\\测试文件.rar");
        File file2 = new File("F:\\temporary\\writeFile\\测试文件.rar");
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(file2);
        Long startTime = System.currentTimeMillis();
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        MappedByteBuffer mappedByteBuffer = null;
        try {
     
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();
            mappedByteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            outChannel.write(mappedByteBuffer);

            Long endTime = System.currentTimeMillis();
            System.out.println("MappedByteBuffer 1G 文件耗时: "+(endTime - startTime));

        }catch (Exception e) {
     
            e.printStackTrace();
        }finally {
       
            mappedByteBuffer.clear();
            inChannel.close();
            outChannel.close();
        }   
    }

实验运行结果:
在这里插入图片描述
在这里插入图片描述

二、问题

正如运行结果所展示,我们会发现,对于一个大文件,transferFrom 竟然会比 MappedByteBuffer 这种方式快得多,这里面的原因是什么,为什么会出现这样的结果,于是我在 CSDN 和 开源中国上提出了这个问题,以下是各个大佬们的回答:

在这里插入图片描述
NIO——transferFrom/transferTo与MappedByteBuffer问题/实验_第1张图片

三、看了大佬们的回答,我自己做了啥

其他人的说法都是偏理论性的,而"妹子楼顶有鸽子"有理有据有参考,所以,我便从他所说的地方去着手。
① transfer 底层就是使用的 MappedByteBuffer,这个是真的吗?
② sun.nio.ch.FileChannelImpl.transferFromFileChannel 方法里面是怎么样实现的?

抱着认真严谨的态度以及一丝好奇,开始查看:

Ⅰ、先查看获取的 channel

① FileInputStream 获取的 channel 对象,源码如下:

 public FileChannel getChannel() {
     
        synchronized (this) {
     
            if (channel == null) {
     
                channel = FileChannelImpl.open(fd, path, true, false, this);
            }
            return channel;
        }
    }

② FileOutputStream获取的 channel 对象,源码如下:

public FileChannel getChannel() {
     
        synchronized (this) {
     
            if (channel == null) {
     
                channel = FileChannelImpl.open(fd, path, false, true, append, this);
            }
            return channel;
        }
    }

在这里我们可以发现, 获取的 FileChannel 对象都与 FileChannelImpl 有关,FileInputStream 和 FileOutputStream 获取 FileChannel 的区别暂时看起来只是 FileChannelImpl 传入的参数不同,同时map 和 transferXX 方法都与 FileChannel 有关, 那我们便看看 FileChannelImpl 里面做了些什么吧!

首先便是 FileChannelImpl 对应的两个 open 方法,

 // FileInputStream 获取通道时调用的方法
  public static FileChannel open(FileDescriptor var0, String var1, boolean var2, boolean var3, Object var4) {
     
        return new FileChannelImpl(var0, var1, var2, var3, false, var4);
    }
 // FileOutputStream 获取通道时调用的方法
 public static FileChannel open(FileDescriptor var0, String var1, boolean var2, boolean var3, boolean var4, Object var5) {
     
        return new FileChannelImpl(var0, var1, var2, var3, var4, var5);
    }

由上面可以看到,他们都调用了同一个方法来获取 FileChannel ,不同之处在于所传参数的不同,那这些参数是什么意思呢,这里以 FileOutputStream 的创建调用方法的参数来加以说明。

参数名 参数意义
var0 这是一个文件描述符类,用作表示打开文件,开放套接字或其它字节元或信宿的底层机器特定结构的不透明句柄。文件描述符的主要实际用途是创建一个FileInputStream 或 FileOutputStream 来包含它
var1 路径 path
var2 布尔值,具体作用这里看不出来(源码往下看才知道,这里表示 readable )
var3 布尔值,具体作用这里看不出来(源码往下看才知道,这里表示 writable )
var4 为真表示文件为追加打开
var5 表示一个流对象

那接下来再看 FileChannelImpl 的构造器吧!它的源码实现如下:

 private FileChannelImpl(FileDescriptor var1, String var2, boolean var3, boolean var4, boolean var5, Object var6) {
     
        this.fd = var1;
        this.readable = var3;  // 从这里可以看出,这里的参数 var3 var4 表示读写
        this.writable = var4;  
        this.append = var5;
        this.parent = var6;
        this.path = var2;
        this.nd = new FileDispatcherImpl(var5);
    }

至此我们获取 FileChannel 完成,接下来先看 FileChannel 的 map 方法吧!

Ⅱ、FileChannelImpl 的 map 方法

拓展知识点:assert 断言参考
说明 :
assert 是个宏,它的作用是如果它的条件返回错误,则终止程序执行。
在调试结束后,可以通过在包含 #include 的语句之前插入 #define NDEBUG 来禁用 assert 调用,如:

#include 
#define NDEBUG 
#include

缺点:
频繁的调用会极大的影响程序的性能,增加额外的开销

注意:
① 每个 assert 只验证一个条件,因为同时检验多个条件时,如果断言失败,无法只管的判断是哪个条件失败
② 不能使用改变环境的语句,因为 assert 只在 DEBUG 个生效(底层),如果这么做,会使用程序在正真运行时遇到问题。如:
assert(i++<100); 这是因为如果出错,比如在执行前 i=100,那么这条语句就不会执行,那么i++这条命令就没有执行
③ assert 和后面的语句应空一行,以形成逻辑和视觉上的一致感
④ 有的地方assert 不能代替条件过滤

断言使用的几个原则:
① 使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况的区别,后者是必然存在的并且是一定要作出处理的
② 使用断言对函数的参数进行确认
③ 在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?” 一旦确定了的假定,就要使用断言对假定进行检查
④ 一般教科书都鼓励程序员们进行防错性的程序涉及,但要记住这种编程风格会隐瞒错误。当进行防错性编程时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警

map 方法的源码有点儿长,参数变量也有点儿多,这里先列个表格解释一下它的参数以及意义吧,免得弄混了。

参数 参数意义( 这里的参数仅限于这个方法,别的方法没看 )
var1 MapMode 类型,表示映射类型 ,有三个,分别是只读(READ_ONLY) 、读写(READ_WRITE) 、私有(PRIVATE)
var2 long 类型,映射区域要启动文件的位置,必须为非负
var4 long 类型,要映射的区域的大小,必须是非负数,不得大于 Integer.MAX_VALUE (这里可能会使你有点儿疑惑,特此说明: 虽然这里的参数是 long 类型,但在 FileChannel 接口中,给出的是不得大于 Integer.MAX_VALUE,改方法也做了对应的判断 )
var6 byte类型, 初始值为 -1 ; var1是 READ_ONLY 时,值为 0;是 READ_WRITE 时,值为 1; 是 PRIVATE 时,值为 2 ;用于断言判断
var7 long 类型,初始值为 -1 ; 表示的是逻辑地址
var9 int 类型, 初始值为 -1 ; var1是 READ_ONLY 时,值为 0;是 READ_WRITE 时,值为 1; 是 PRIVATE 时,值为 2
var10 long 类型, 映射大小;
var12 int 类型,;
var13 Object类型,这个对象是每个 FileChannelImpl 的私有属性对象, 用于锁 ;
var14 long 类型,作用暂时未知
var16 long 类型,作用暂时未知
var37 long 类型,映射位置;
var38 MappedByteBuffer 类型,初始值为 -1 ; var1是 READ_ONLY 时,值为 0;是 READ_WRITE 时,值为 1; 是 PRIVATE 时,值为 2
   public MappedByteBuffer map(MapMode var1, long var2, long var4) throws IOException {
     
        this.ensureOpen();   // 确保打开
        if (var1 == null) {
     
            throw new NullPointerException("Mode is null");
        } else if (var2 < 0L) {
     
            throw new IllegalArgumentException("Negative position");
        } else if (var4 < 0L) {
     
            throw new IllegalArgumentException("Negative size");
        } else if (var2 + var4 < 0L) {
     
            throw new IllegalArgumentException("Position + size overflow");
        } else if (var4 > 2147483647L) {
     
            throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");
        } else {
     
            byte var6 = -1;   // 根据传来的  MapMode 来给 var6 赋值
            if (var1 == MapMode.READ_ONLY) {
     
                var6 = 0;
            } else if (var1 == MapMode.READ_WRITE) {
     
                var6 = 1;
            } else if (var1 == MapMode.PRIVATE) {
     
                var6 = 2;
            }

            assert var6 >= 0;  // 断言,它是一个宏,作用是如果它的条件返回错误,则终止程序执行

			// 如果传入的是非只读 并且 当前是 非写入,则抛出异常
            if (var1 != MapMode.READ_ONLY && !this.writable) {
     
                throw new NonWritableChannelException();
            } else if (!this.readable) {
     
                throw new NonReadableChannelException();
            } else {
     
                long var7 = -1L;
                int var9 = -1;

                MappedByteBuffer var38;
                try {
     
                    this.begin();   // 开始,这个方法主要是做标记,标记文件打开状态、线程等信息
                    var9 = this.threads.add(); // 主要对里面的 elts 属性进行了操作
                    if (!this.isOpen()) {
        // 文件没有被打开
                        Object var34 = null;
                        return (MappedByteBuffer)var34;
                    }

                    Object var13 = this.positionLock;  // 获取锁对象
                    long var10;
                    int var12;
                    synchronized(this.positionLock) {
      // 加锁
                        long var14;
                        do {
     
                        	// fd 是获取通道是的第一个参数 FileDescriptor,文件描述符类
                        	// this.nd 是获取通道对象的是,在 FileChannelImpl 构造方法中,new 出来的对象FileDispatcherImpl,传入的参数是var5(追加的方式)  
                        	// FileDispatcherImpl 的 size 方法,调用了本地方法 size0(FileDescriptor var0)
                            var14 = this.nd.size(this.fd);
                        } while(var14 == -3L && this.isOpen()); // 这个地方还不大明白,返回值表示一个大小,但具体代表什么大小?

                        if (!this.isOpen()) {
      
                            var38 = null;
                            return var38;
                        }

                        MappedByteBuffer var17;
                        if (var14 < var2 + var4) {
      // var2代表映射区域启动文件位置  var4表示映射区域大小,var2+var4 则表示最大位置,推测 var14 表示位置信息,那么问题来了,为什么不判断 var2 < var14  呢? 既然不知道,就继续向下看吧
                            if (!this.writable) {
     
                                throw new IOException("Channel not open for writing - cannot extend file to required size");
                            }

                            int var16;
                            do {
     
                            // this.fd 表示 FileDescriptor 文件描述符对象,这个对象是创建通道时传来的
                            // allocate 看字面意思是分配的意思 ,它的方法底层仍是调用的本地方法  truncate0(var1, var2),返回一个 int 类型的值  这个值什么含义  暂时未知   继续向下看吧!
                                var16 = this.nd.allocate(this.fd, var2 + var4);
                            } while(var16 == -3 && this.isOpen());

                            if (!this.isOpen()) {
     
                                var17 = null;
                                return var17;
                            }
                        }

                        if (var4 == 0L) {
       // 如果传入映射文件区域大小为 0
                            var7 = 0L;  
                            FileDescriptor var39 = new FileDescriptor();  // 重新创建了一个文件描述对象
                            if (this.writable && var6 != 0) {
       // 可写 并且 不是只读(0 表示只读,看上面)
                              // 使用 Util 工具类,创建了一个 MappedByteBuffer 对象
                                var17 = Util.newMappedByteBuffer(0, 0L, var39, (Runnable)null); 
                                return var17;
                            }
							
							// 如果上面的if 条件不满足,便执行下面这条语句,仍然是使用 Util 类生成一个 MappedByteBuffer,但注意上面调的方法 和 这个调用的方法不是同一个
                            var17 = Util.newMappedByteBufferR(0, 0L, var39, (Runnable)null);
                            return var17;
                        }
						
						// allocationGranularity 看英文为分配粒度的意思 ,它是在类中的定义是:
						//  private static final long allocationGranularity;
						// 在静态代码块中被初始化,调用的是本地方法 initIDs()
						// var2 是映射区域要启动文件的位置
						// var4 要映射的区域的大小
                        var12 = (int)(var2 % allocationGranularity);
                        long var37 = var2 - (long)var12;
                        var10 = var4 + (long)var12;

                        try {
     
                        	// 这是一个本地方法,看方法名可推测是做映射
                        	// var6   0(READ_ONLY)   1(READ_WRITE)  2(PRIVATE)
                        	// var7   逻辑地址
                        	// var37  映射位置
                        	// var10  映射大小
                            var7 = this.map0(var6, var37, var10);
                        } catch (OutOfMemoryError var31) {
     
                            System.gc();
							
                            try {
       // 出现异常,线程休眠0.1秒,并等待gc
                                Thread.sleep(100L);
                            } catch (InterruptedException var30) {
     
                                Thread.currentThread().interrupt();
                            }

                            try {
      // 重试一次,如果再失败,则抛出映射失败的异常
                                var7 = this.map0(var6, var37, var10);
                            } catch (OutOfMemoryError var29) {
     
                                throw new IOException("Map failed", var29);
                            }
                        }
                    }

                    FileDescriptor var35;
                    try {
      // 重复映射
                        var35 = this.nd.duplicateForMapping(this.fd);
                    } catch (IOException var28) {
     
                        // 出现异常,放弃映射
                        unmap0(var7, var10);
                        throw var28;
                    }
					// 断言检查IO 的状态
                    assert IOStatus.checkAll(var7); 

                    assert var7 % allocationGranularity == 0L;

					// 
                    int var36 = (int)var4;
                    FileChannelImpl.Unmapper var15 = new FileChannelImpl.Unmapper(var7, var10, var36, var35, null);
                    if (!this.writable || var6 == 0) {
     
                        var38 = Util.newMappedByteBufferR(var36, var7 + (long)var12, var35, var15);
                        return var38;
                    }

                    var38 = Util.newMappedByteBuffer(var36, var7 + (long)var12, var35, var15);
                } finally {
     
                    this.threads.remove(var9);
                    this.end(IOStatus.checkAll(var7));
                }

                return var38;
            }
        }
    }

① Util 工具类的 newMappedByteBuffer 方法

static MappedByteBuffer newMappedByteBuffer(int var0, long var1, FileDescriptor var3, Runnable var4) {
     
        if (directByteBufferConstructor == null) {
     
            initDBBConstructor();
        }

        try {
     
            MappedByteBuffer var5 = (MappedByteBuffer)directByteBufferConstructor.newInstance(new Integer(var0), new Long(var1), var3, var4);
            return var5;
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException var7) {
     
            throw new InternalError(var7);
        }
    }

② Util 工具类的 newMappedByteBufferR方法

static MappedByteBuffer newMappedByteBufferR(int var0, long var1, FileDescriptor var3, Runnable var4) {
     
        if (directByteBufferRConstructor == null) {
     
            initDBBRConstructor();
        }

        try {
     
            MappedByteBuffer var5 = (MappedByteBuffer)directByteBufferRConstructor.newInstance(new Integer(var0), new Long(var1), var3, var4);
            return var5;
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException var7) {
     
            throw new InternalError(var7);
        }
    }

不知道你有没有和我一样,看完了还是感觉很懵,变量太多,而且还不知道它表示的是什么意思,有点儿令人抓狂,但这有什么关系呢,至少对大概有了个了解,认知本来就是一个增量的过程。接下来看看 它的write 方法是如何写入的吧!!!

Ⅱ、FileChannel 的 write 方法

FileChannel 有两个 write 方法,分别是:

public int write(ByteBuffer var1) throws IOException
public long write(ByteBuffer[] var1, int var2, int var3) throws IOException

先看第一个write 方法吧

   public int write(ByteBuffer var1) throws IOException {
     
        this.ensureOpen();
        if (!this.writable) {
     
            throw new NonWritableChannelException();
        } else {
     
            Object var2 = this.positionLock;  // 获取私有锁对象
            synchronized(this.positionLock) {
       // 加锁
                int var3 = 0;
                int var4 = -1;

                byte var5;
                try {
     
                    this.begin();
                    var4 = this.threads.add();
                    if (this.isOpen()) {
     
                        do {
     
                        	// 使用 IOUtil 工具类,进行写操作
                        	// this.fd 文件描述对象
                        	// var1 ByteBuffer 对象
                        	// -1L 含义未知
                        	// this.nd 文件视图对象
                            var3 = IOUtil.write(this.fd, var1, -1L, this.nd);
                        } while(var3 == -3 && this.isOpen());

                        int var12 = IOStatus.normalize(var3);
                        return var12;
                    }

                    var5 = 0;
                } finally {
     
                    this.threads.remove(var4);
                    this.end(var3 > 0);

                    assert IOStatus.check(var3);

                }
                return var5;
            }
        }
    }

第二个参数就不看了吧,看 IOUtil 的 write 方法,改方法如下:

   static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
     
        if (var1 instanceof DirectBuffer) {
      // 如果 var1 是 DirectBuffer 的子类,直接调用了从本地缓冲写方法(writeFromNativeBuffer)
            return writeFromNativeBuffer(var0, var1, var2, var4);
        } else {
     
            int var5 = var1.position();
            int var6 = var1.limit();

            assert var5 <= var6;

            int var7 = var5 <= var6 ? var6 - var5 : 0;
            ByteBuffer var8 = Util.getTemporaryDirectBuffer(var7);  // 在这里获取临时DirectBuffer

            int var10;
            try {
     
                var8.put(var1);
                var8.flip();
                var1.position(var5);
                int var9 = writeFromNativeBuffer(var0, var8, var2, var4); // 然后再调用 writeFromNativeBuffer 方法
                if (var9 > 0) {
     
                    var1.position(var5 + var9);
                }

                var10 = var9;
            } finally {
     
               // 里面会有个判断,判断是否太大(isBufferTooLarge方法),会进行释放操作(free方法)
                Util.offerFirstTemporaryDirectBuffer(var8);
            }

            return var10;
        }
    }

至此,FileChannel 的 map 方法就大致看完了,接下来看看 transferFrom 方法

Ⅲ、FileChannel 的 transferFrom/transferTo 方法

public long transferFrom(ReadableByteChannel var1, long var2, long var4) throws IOException {
     
        this.ensureOpen();
        if (!var1.isOpen()) {
     
            throw new ClosedChannelException();
        } else if (!this.writable) {
     
            throw new NonWritableChannelException();
        } else if (var2 >= 0L && var4 >= 0L) {
     
            if (var2 > this.size()) {
     
                return 0L;
            } else {
     
                // 在上面的代码中提到过,通道的获取是通过在静态方法中 new FileChannelImpl , 所以此处 我们应该看的是 transferFromFileChannel 方法
                return var1 instanceof FileChannelImpl ? this.transferFromFileChannel((FileChannelImpl)var1, var2, var4) : this.transferFromArbitraryChannel(var1, var2, var4);
            }
        } else {
     
            throw new IllegalArgumentException();
        }
    }

transferFromFileChannel 方法源码如下:

// var1 是通道对象 
// var2 是传输开始的文件中的位置,必须是非负的
// var4 要传输的最大字节数
 private long transferFromFileChannel(FileChannelImpl var1, long var2, long var4) throws IOException {
     
        if (!var1.readable) {
     
            throw new NonReadableChannelException();
        } else {
     
            Object var6 = var1.positionLock; // 获得锁对象
            synchronized(var1.positionLock) {
      // 加锁 
                long var7 = var1.position(); // position 方法返回此通道的文件位置
                long var9 = Math.min(var4, var1.size() - var7); //size方法返回的是此通道文件的当前大小
                long var11 = var9;
                long var13 = var7;

                long var15;
                while(var11 > 0L) {
     
                    var15 = Math.min(var11, 8388608L); // 8*1024*1024 = 8388608L
                    MappedByteBuffer var17 = var1.map(MapMode.READ_ONLY, var13, var15); // 这里调用了 map 方法

                    try {
     
                        long var18 = (long)this.write(var17, var2);  // 然后再调用 write 方法

                        assert var18 > 0L;

                        var13 += var18;
                        var2 += var18;
                        var11 -= var18;
                    } catch (IOException var25) {
     
                        if (var11 != var9) {
     
                            break;
                        }

                        throw var25;
                    } finally {
     
                        unmap(var17);
                    }
                }

                var15 = var9 - var11;
                var1.position(var7 + var15);
                return var15;
            }
        }
    }

查看了上面的源码,会发现,transferFrom 方法在底层仍然是调用的是通道的 map 方法来获取到 MappedByteBuffer 对象,这么说的话,它们本质上仍是一样的,那为什么对于同一个大文件的处理所话非的时间会相差这么多呢? 可能你会注意到在 transferFrom 中有这么一段代码,如下:

 	var15 = Math.min(var11, 8388608L);  // 8*1024*1024  = 8388608 
    MappedByteBuffer var17 = var1.map(MapMode.READ_ONLY, var13, var15); 

那么我们可以推测,是不是因为 要映射的区域的大小太大了导致性能差距如此之大的原因,于是启动 Debug,去查看 map 方法映射文件大小在最上面两次实验的值是多少,运行结果如图:

第一次运行,结果如图:
在这里插入图片描述
第二次运行,结果如图:
在这里插入图片描述
Debug 的结果似乎如猜想的那般,这是由于要映射的区域太大而导致的性能问题。既然如此,就用实验去证明是因为文件映射

映射区域设置过大影响性能验证实验:

实验说明:
文件大小: 1.12G
已知将映射区域设置为文件大小时,耗时为 7961 (数据来源为上面的实验)
实验思路:
① 将 transferFrom 方法内部核心代码复制,
② 重新封装为一个方法
③ 不断修改映射区域大小,查看对应的耗时情况,(因为文件文件1.12G,足够大,修改映射区域比它小就行)
实验代码如下(因为是实验复制的源码,要稍少改一点儿东西,不要在意那些不重要的细节):

  public static void main(String[] args) throws Exception {
     
        System.out.println("F:\\temporary\\write.txt");

        File file = new File("F:\\temporary\\readFile\\测试文件.rar");
        File file2 = new File("F:\\temporary\\writeFile\\测试文件.rar");
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(file2);
        Long startTime = System.currentTimeMillis();
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
     
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();

            transferFromFileChannel((FileChannelImpl)inChannel, 0, inChannel.size(),(FileChannelImpl)outChannel);

            Long endTime = System.currentTimeMillis();
            System.out.println("MappedByteBuffer 1G 文件耗时: "+(endTime - startTime));
        }catch (Exception e) {
     
            e.printStackTrace();
        }finally {
     
            inChannel.close();
            outChannel.close();
        }

    }

    public static  long transferFromFileChannel(FileChannelImpl var1, long var2, long var4,FileChannelImpl fileChannel) throws IOException {
     
            long var7 = var1.position();
            long var9 = Math.min(var4, var1.size() - var7);
            long var11 = var9;
            long var13 = var7;

            long var15;
            while(var11 > 0L) {
     
                var15 = Math.min(var11, 2*8388608L);
                MappedByteBuffer var17 = var1.map(FileChannel.MapMode.READ_ONLY, var13, var15);

                try {
     
                    long var18 = fileChannel.write(var17, var2);

                    assert var18 > 0L;

                    var13 += var18;
                    var2 += var18;
                    var11 -= var18;
                } catch (IOException var25) {
     
                    if (var11 != var9) {
     
                        break;
                    }

                    throw var25;
                } finally {
     
                    unmap(var17);
                }
            }

            var15 = var9 - var11;
            var1.position(var7 + var15);
            return var15;
    }
    public static  void unmap(MappedByteBuffer var0) {
     
        Cleaner var1 = ((DirectBuffer)var0).cleaner();
        if (var1 != null) {
     
            var1.clean();
        }

    }

为了实验效果,将var15 设置为 8388608 的整数倍, 修改var15 的参数,结果如下:

1213895375(文件大小) / 8388608 = 144.7 ,也就是说整数倍设置小于 140 就行了

var15 参数的值 耗时
8388608L/4 1877
8388608L/2 1991
8388608L 1665
2*8388608L 1853
4*8388608L 4478
8*8388608L 6427
10*8388608L 6937
20*8388608L 7262
30*8388608L 7551
50*8388608L 8064
100*8388608L 7827
130*8388608L 7853

实验结果,也确实证明了与映射区域的大小有关

总结:
① 使用 transferFrom 本质上还是在使用 MappedByteBuffer,只是将其进行了封装
② 使用通道的 map 方法时,如果将映射区域设置得过大,仍然会导致性能问题

你可能感兴趣的:(Java基础学习,java)