java filechannel 空_java-nio基础-4-FileChannel

本文详细介绍了Java NIO中的FileChannel,包括它的线程安全性、文件访问、文件空洞、文件锁和内存映射文件。重点讨论了内存映射文件的高效性和并发修改的注意事项,以及FileChannel的传输方法。通过实例展示了FileChannel在文件复制中的性能优势。
摘要由CSDN通过智能技术生成

一、

介绍

a4c26d1e5885305701be709a3d33442f.png

文件channel总是阻塞式的,因此不能被置于非阻塞模式。现代操作系统都有复杂的缓存和预取机制,使得本地磁盘I/O 操作延迟很少。网络文件系统一般而言延迟会多些,不过却也因该优化而受益。面向流的 I/O 的非阻塞范例对于面向文件的操作并无多大意义,这是由文件 I/O 本质上的不同性质造成的。对于文件 I/O,最强大之处在于异步

I/O(asynchronous I/O),它允许一个进程可以从操作系统请求一个或多个

I/O 操作而不必等待这些操作的完成。发起请求的进程之后会收到它请求的 I/O 操作已完成的通知。

FileChannel不能被直接创建,只能通过调用一个打开文件对象的getChannel方法,这些文件对象包括RandomAccessFile,

FileInputStream或FileOutputStream。通过getChannel方法得到的FileChannel连接到了同样的文件,并具有文件对象同样的访问权限。

FileChannel是线程安全的。多个线程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的(multithreaded)。影响channel位置或者影响文件大小的操作都是单线程的(single-threaded)。如果有一个线程已经在执行会影响channel位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待。并发行为也会受到底层的操作系统或文件系统影响。

二、

文件访问

1.

基本概念

每个FileChannel和文件descriptor都有一对一的关系,所以FileChannel的API和我们通用文件I/O系统紧密相关(POSIX及其它系统)。RandomAccessFile也有类似的方法。

同底层的文件描述符descriptor一样,每个FileChannel 都有一个叫“file

position”的概念。这个

position 值决定文件中哪一处的数据接下来将被读或者写。从这个方面看,FileChannel 类同buffer很类似,并且

MappedByteBuffer

类使得我们可以通过 ByteBuffer

API 来访问文件数据(我们会在后面的章节中了解到这一点)

您可以从前面的API清单中看到,有两种形式的position( )方法。第一种,不带参数的,返回当前文件的position值。

第二种形式的position(

)方法带一个

long(长整型)参数并将通道的

position 设置为指定值。如果尝试将channel的

position 设置为一个负值会导致 java.lang.IllegalArgumentException 异常,不过可以把 position

设置到超出文件尾,这样做会把 position 设置为指定值而不改变文件大小。假如在将position 设置为超出当前文件大小时实现了一个 read(

)方法,那么会返回一个文件尾(end-of-file)条件;倘若此时实现的是一个

write( )方法则会引起文件增长以容纳写入的字节,具体行为类似于实现一个绝对

位置write(byte[], position

)并可能导致出现一个文件空洞(file hole)。

类似于buffer的

get( ) 和 put(

)方法,当字节被

read( )或

write( )方法传输时,文件

position 会自动更新。如果 position

值达到了文件大小的值(文件大小的值可以通过 size( )方法返回),read( )方法会返回一个文件尾条件值(-1)。可是,不同于buffer的是,如果实现

write( )方法时

position前进到超过文件大小的值,该文件会扩展以容纳新写入的字节

同样类似于buffer,也有带

position 参数的绝对形式的 read(byte[],

position)和

write(byte[], position

)方法。这种绝对形式的方法在返回值时不会改变当前的文件

position。由于channel的状态无需更新,因此绝对的读和写可能会更加有效率,操作请求可以直接传到本地代码。更妙的是,多个线程可以并发访问同一个文件而不会相互产生干扰。这是因为每次调用都是原子性的(atomic),并不依靠调用之间系统所记住的状态。

2.

文件空洞

下面通过实例来介绍什么是文件空洞。文件空洞通常出现在磁盘为文件分配的空间小于文件大小时出现。大多数限定文件系统对于稀疏文件,通常会只为正在写在文件上的数据分配空间(更恰当来说,只为那些已经写了数据的文件系统页面)。如果在不连续的位置写入数据,就可能导致不包含数据的文件区域(空洞),如下图所示:

a4c26d1e5885305701be709a3d33442f.png

Linux系统实际分配的空间的确去除了中间的空洞,而windows则没有,大小为48MB,占用磁盘空间48MB。

a4c26d1e5885305701be709a3d33442f.png

package com.scn7th.channel;

import com.scn7th.util.FileUtil;

import java.io.*;

import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;public class MyFileChannel {

public void fileWriteHole() {

File file = new File("file/CopyTarget.txt");

file.delete();

FileUtil.createFile(file);

try (FileOutputStream outputStream =

new FileOutputStream(file)){

FileChannel channel = outputStream.getChannel();

ByteBuffer byteBuffer =

ByteBuffer.allocateDirect(100);

writeData(0, byteBuffer, channel);

writeData(5000000, byteBuffer, channel);

writeData(50000, byteBuffer, channel);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

private void writeData(long position, ByteBuffer buffer, FileChannel channel) throws IOException {

try {

byte[] strings = "hello man!".getBytes("US-ASCII");

buffer.clear();

buffer.put(strings);

buffer.flip();

channel.position(position);channel.write(buffer);

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

}

}

3.

truncate

如果我们需要减少一个文件的大小,truncate(long size)方法就可以砍掉超过指定文件大小后的数据。如果size大于文件原大小,不会减小文件大小。Truncate后,文件的position都会被置于文件真实大小位置。这里需要注意,通过FileOutputStream再次打开文件,必须设置append=true,否则会自动清空原文件。

public voidtruncate() {

File file =newFile("file/CopyTarget.txt");//如果FileOutputStream不是append模式,每次打开都会清空原文件中的所有数据try(FileOutputStream outputStream =newFileOutputStream(file, true)){

FileChannel channel = outputStream.getChannel();System.out.println("文件大小:"+ channel.size() +";position:"+ channel.position());channel.truncate(500);System.out.println("文件大小:"+ channel.size() +";position:"+ channel.position());}catch(FileNotFoundException e) {

e.printStackTrace();}catch(IOException e) {

e.printStackTrace();}

}

4.

force

和RandomAccessFile的getFD().sync()方法一样,我们可以通过force将内存中的文件数据刷新到硬盘中。Sync()方法强制操作系统buffer将它们所持有的数据写入到真正的硬件中,从而清空操作系统buffer。通常来说,我们会在刷新到硬件sync()前调用flush。Flush用于清空java内部的buffer。Sync则用于清空操作系统、硬件设备驱动的buffer。

FIleChannel的Force也是如此。FIleChannel实际操作的就是File Discriptor。Force(Boolean metaData)的入参表示是否将文件的metaData也写入硬盘。元数据指文件所有者、访问权限、最后一次修改时间等信息。大多数情形下,该信息对数据恢复而言是不重要的。给

force( )方法传递

false 值表示在方法返回前只需要同步文件数据的更改。大多数情形下,同步元数据要求操作系统进行至少一次额外的底层

I/O 操作。一些大数量事务处理程序可能通过在每次调用 force( )方法时不要求元数据更新来获取较高的性能提升,同时也不会牺牲数据完整性。

三、

文件锁

1.

原理

在 JDK 1.4

版本之前,Java I/O

模型都未能提供文件锁定(file

locking),缺少这一特性让人们很头疼。绝大多数现代操作系统早就有了文件锁定功能,而直到

JDK 1.4 版本发布时 Java

编程人员才可以使用文件锁(file

lock)。在集成许多其他非

Java 程序时,文件锁定显得尤其重要。此外,它在判优(判断多个访问请求的优先级别)一个大系统的多个 Java 组件发起的访问时也很有价值。

我们在第一章中讨论到,锁(lock)可以是共享的(shared)或独占的(exclusive)。本节中描述的文件锁定特性在很大程度上依赖本地的操作系统实现。并非所有的操作系统和文件系统都支持共享文件锁。对于那些不支持的,对一个共享锁的请求会被自动提升为对独占锁的请求。这可以保证准确性却可能严重影响性能。

另外,并非所有平台都以同一个方式来实现基本的文件锁定。在不同的操作系统上,甚至在同一个操作系统的不同文件系统上,文件锁定的语义都会有所差异。一些操作系统仅提供劝告锁定(advisory locking),一些仅提供独占锁(exclusive locks),而有些操作系统可能两种锁都提供。您应该总是按照劝告锁的假定来管理文件锁,因为这是最安全的。但是如能了解底层操作系统如何执行锁定也是非常好的。例如,如果所有的锁都是强制性的(mandatory)而您不及时释放您获得的锁的话,运行在同一操作系统上的其他程序可能会受到影响。

有关 FileChannel

实现的文件锁定模型的一个关键注意项是:锁的对象是文件而不是通道或线程,文件锁是通过JVM虚拟机进程获得,多个JVM进程对上锁文件操作是进程安全的,但同一JVM进程内部操作同一个上锁文件则不是线程安全的!

例如,某JVM进程获得了文件锁,其中Channel 1中的线程也可以重入锁从而修改文件,Channel 2中的线程也可以重入锁修改文件!但如果这两个线程运行在不同的

Java 虚拟机上,那么第二个线程会阻塞,因为锁最终是由操作系统或文件系统来判优的并且几乎总是在进程级而非线程级上判优。

2.

基本方法

1)

带参数的lock

这次我们先看带参数形式的 lock(

)方法。锁是在文件内部区域上获得的。调用带参数的

Lock( )方法会指定文件内部锁定区域的开始

position 以及锁定区域的 size。第三个参数

shared 表示您想获取的锁是共享的(参数值为 true)还是独占的(参数值为

false)。要获得一个共享锁,您必须先以只读权限打开文件,而请求独占锁时则需要写权限。

锁定区域的范围不一定要限制在文件的size 值以内,锁可以扩展从而超出文件尾。因此,我们可以提前把待写入数据的区域锁定,我们也可以锁定一个不包含任何文件内容的区域,比如文件最后一个字节以外的区域。如果之后文件增长到达那块区域,那么您的文件锁就可以保护该区域的文件内容了。相反地,如果您锁定了文件的某一块区域,然后文件增长超出了那块区域,那么新增加的文件内容将不会受到您的文件锁的保护。

2)

不带参数lock

不带参数的简单形式的 lock(

)方法是一种在整个文件上请求独占锁的便捷方法,锁定区域等于它能达到的最大范围。该方法等价于:

fileChannel.lock (0L, Long.MAX_VALUE,

false);

3)

tryLock

tryLock( )的方法它们是

lock( )方法的非阻塞变体。这两个tryLock( )和

lock( )方法起相同的作用,不过如果请求的锁不能立即获取到则会返回一个null。

4)

validarity

一个 FileLock

对象创建之后即有效,直到它的 release(

)方法被调用或它所关联的通道被关闭或Java 虚拟机关闭时才会失效。我们可以通过调用 isValid( )布尔方法来测试一个锁的有效性。一个锁的有效性可能会随着时间而改变,不过它的其他属性——位置(position)、范围大小(size)和独占性(exclusivity)——在创建时即被确定,不会随着时间而改变。

5)

overlap锁重叠

最后,您可以通过调用 overlaps(

)方法来查询一个

FileLock 对象是否与一个指定的文件区域重叠。这将使您可以迅速判断您拥有的锁是否与一个感兴趣的区域(region of interest)有交叉。不过即使返回值是

false 也不能保证您就一定能在期望的区域上获得一个锁,因为 Java 虚拟机上的其他地方或者外部进程可能已经在该期望区域上有一个或多个锁了。您最好使用 tryLock( )方法确认一下。

四、

内存映射文件

新的 FileChannel

类提供了一个名为 map(

)的方法,该方法可以在一个打开的文件和一个特殊类型的

ByteBuffer 之间建立一个虚拟内存映射(第一章中已经归纳了什么是内存映射文件以及它们如何同虚拟内存交互)。在 FileChannel 上调用 map(

)方法会创建一个由磁盘文件支持的虚拟内存映射(virtual memory mapping)并在那块虚拟内存空间外部封装一个

MappedByteBuffer

对象。

a4c26d1e5885305701be709a3d33442f.png

1.

Map方法

map( )方法返回的MappedByteBuffer 对象的行为在多数方面类似一个基于内存的缓冲区,只不过该对象的数据元素存储在磁盘上的一个文件中。调用

get( )方法会从磁盘文件中获取数据,此数据反映该文件的当前内容,即使在映射建立之后文件已经被一个外部进程做了修改。通过文件映射看到的数据同您用常规方法读取文件看到的内容是完全一样的。相似地,对映射的缓冲区实现一个

put( )会更新磁盘上的那个文件(假设对该文件您有写的权限),并且您做的修改对于该文件的其他阅读者也是可见的。

所以对内存映射文件的修改,对于其他线程是内存可见的。

通过内存映射机制获取文件比传统读写数据要高效的多,即使我们使用Channel。我们不需要明确的进行耗时的系统调用。更重要的是,操作系统的虚拟内存系统可以自动的缓存内存分页。这些分页可以通过系统内存来进行缓存,而不需要消耗JVM的堆内存(和direct buffer一样)。

一旦内存页面有效(从磁盘中读取到kernel

memory),我们就可以不用另外的系统调用直接通过内存映射访问数据。包含索引或其他被频繁引用、更新的数量大、结构化的文件可以通过内存映射获得巨大性能提升。通过与文件锁集合以保证文件重要部分并控制事务原子性时,你可以发现MappedByteBuffer(专门供memory-mapped file使用的direct buffer)就能够有效使用。

与文件锁的范围机制不一样,映射文件的范围不应超过文件的实际大小。如果您请求一个超出文件大小的映射,文件会被增大以匹配映射的大小。假如您给

size 参数传递的值是Integer.MAX_VALUE,文件大小的值会膨胀到超过

2.1GB。即使您请求的是一个只读映射,map( )方法也会尝试这样做并且大多数情况下都会抛出一个

IOException 异常,因为底层的文件不能被修改。该行为同之前讨论的文件“空洞”的行为是一致的。

和传统文件处理异议,文件映射也可以是可写(MapMode.READ_WRITE)和只读(MapMode.READ_ONLY)。第三种模式MapMode.PRIVATE则表明使用copy-on-write映射。也就是说,任何通过put的修改操作会出现一份MappedByteBuffer的私有拷贝,而不会直接改变所依赖的文件。一旦当本地拷贝的buffer被垃圾回收,所有对文件的修改都会丢失。尽管copy-on-write映射阻止了对所依赖文件的修改,但你必须打开文件设置MapMode.PRIVATE映射,这对运行返回的MappedByteBuffer的put方法是必须的。

选择使用 MapMode.PRIVATE 模式并不会导致您的MappedByteBuffer看不到通过其他方式对文件所做的修改(肯定不是copy-on-write模式,因为它无法修改依赖文件)。对文件某个区域的修改在使用

MapMode.PRIVATE

模式的缓冲区中都能反映出来,除非该缓冲区已经修改了文件上的同一个区域。正如第一章中所描述的,内存和文件系统都被划分成了页。当在一个写时拷贝的缓冲区上调用

put( )方法时,受影响的页会被拷贝,然后更改就会应用到该拷贝中。具体的页面大小取决于具体实现,不过通常都是和底层文件系统的页面大小时一样的。如果缓冲区还没对某个页做出修改,那么这个页就会反映被映射文件的相应位置上的内容。一旦某个页因为写操作而被拷贝,之后就将使用该拷贝页,并且不能被其他缓冲区或文件更新所修改。

也就是说如果使用MapMode.PRIVATE模式修改了内存映射第0页,那么其他I/O操作修改了该文件在内存中的第1页,此时就会反应在MapMode.PRIVATE的MappedByteBuffer中。如果其他I/O操作也修改了第0页内容,那么MappedByteBuffer就不会同步改变,仅仅显示本地拷贝已被修改的内容。

注意到不存在unmap方法。一旦映射建立就会一直存在,直到MappedByteBuffer被垃圾回收。和文件锁不同,MappedByteBuffer并没有同创建它们的channel绑定。关闭相关的FileChannel并不会销毁映射。只有销毁MappedByteBuffer本身才会断开映射关系。NIO 设计师们之所以做这样的决定是因为当关闭通道时破坏映射会引起安全问题,而解决该安全问题又会导致性能问题。如果您确实需要知道一个映射是什么时候被破坏的,他们建议使用虚引用(phantom references,参见

java.lang.ref.PhantomReference)和一个

cleanup 线程。不过有此需要的概率是微乎其微的。

2.

并发修改依赖文件

MemoryMappedBuffer直观反映了所关联的磁盘文件。如果在映射生效期间,这个文件在结构上被修改,可能会导致奇怪的情形。MemoryMappedBuffer的大小是固定的,但其映射的文件却是可变的。特别的,如果文件大小发生改变,一些或者全部的MemoryMappedBuffer都不可访问,或者出现未定义的数据返回,或者抛出unchecked exceptions。所以在操作内存映射文件时,必须小心多线程或外部进程的修改。

所有的 MappedByteBuffer 对象都是direct,这意味着它们占用的内存空间位于

Java

虚拟机内存堆之外(并且可能不会算作Java 虚拟机的内存占用,不过这取决于操作系统的虚拟内存模型)。

因为 MappedByteBuffers 也是ByteBuffers,所以能够被传递

SocketChannel

之类通道的 read(

)或write( )以有效传输数据给被映射的文件或从被映射的文件读取数据。如能再结合

scatter/gather,那么从内存缓冲区和被映射文件内容中组织数据就变得很容易了。例

3-4 就是以此方式写 HTTP

响应的。3.4.1

节中将描述一个传输数据给通道或从其他通道读取数据的更加有效的方式。

3.

加载所有文件

load( )方法会加载整个文件以使它常驻内存。正如我们在第一章所讨论的,一个内存映射缓冲区会建立与某个文件的虚拟内存映射。此映射使得操作系统的底层虚拟内存子系统可以根据需要将文件中相应区块的数据读进内存。已经在内存中或通过验证的页会占用实际内存空间,并且在它们被读进

RAM 时会挤出最近较少使用的其他内存页。

在一个MappedByteBuffers上调用

load( )方法会是一个代价高的操作,因为它会导致大量的页调入(page-in),具体数量取决于文件中被映射区域的实际大小。然而,load( )方法返回并不能保证文件就会完全常驻内存,这是由于请求页面调入(demand paging)是动态的。具体结果会因某些因素而有所差异,这些因素包括:操作系统、文件系统,可用

Java 虚拟机内存,最大 Java

虚拟机内存,垃圾收集器实现过程等等。请小心使用 load( )方法,它可能会导致您不希望出现的结果。该方法的主要作用是为提前加载文件埋单,以便后续的访问速度可以尽可能的快。

对于大多数程序,特别是交互性的或其他事件驱动(event-driven)的程序而言,为提前加载文件消耗资源是不划算的。在实际访问时分摊页调入开销才是更好的选择。让操作系统根据需要来调入页意味着不访问的页永远不需要被加载。同预加载整个被映射的文件相比,这很容易减少

I/O 活动总次数。操作系统已经有一个复杂的内存管理系统了,就让它来替您完成此工作吧。

我们可以通过调用 isLoaded(

)方法来判断一个被映射的文件是否完全常驻内存了。如果该方法返回

true 值,那么很大概率是映射缓冲区的访问延迟很少或者根本没有延迟。不过,这也是不能保证的。同样地,返回 false 值并不一定意味着访问缓冲区将很慢或者该文件并未完全常驻内存。isLoaded( )方法的返回值只是一个暗示,由于垃圾收集的异步性质、底层操作系统以及运行系统的动态性等因素,想要在任意时刻准确判断全部映射页的状态是不可能的。

4.

Force()

上面代码中列出的最后一个方法 force(

)同

FileChannel 类中的同名方法相似(参见 3.3.1 节)该方法会强制将映射缓冲区上的更改应用到永久磁盘存储器上。当用 MappedByteBuffer 对象来更新一个文件,应该总是使用 MappedByteBuffer.force( )而非

FileChannel.force(

),因为通道对象可能不清楚通过映射缓冲区做出的文件的全部更改。MappedByteBuffer 没有不更新文件元数据metadata的选择——元数据总是会同时被更新的。

如果映射是以MapMode.READ_ONLY 或 MAP_MODE.PRIVATE 模式建立的,那么调用 force(

)方法将不起任何作用,因为永远不会有更改需要应用到磁盘上(但是这样做也是没有害处的)。

5.

测试

如果使用MapMode.PRIVATE模式修改了内存映射第0页,那么其他I/O操作修改了该文件在内存中的第1页,此时就会反应在MapMode.PRIVATE的MappedByteBuffer中。如果其他I/O操作也修改了第0页内容,那么MappedByteBuffer就不会同步改变,仅仅显示本地拷贝已被修改的内容。

内存映射buffer会实时反应所依赖硬盘文件的变化。

package com.scn7th.channel;

import com.scn7th.util.FileUtil;

import lombok.extern.slf4j.Slf4j;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.nio.ByteBuffer;

import java.nio.MappedByteBuffer;

import java.nio.channels.FileChannel;@Slf4jpublic class MyMemoryMappedFile {

private static int MEMORY_PAGE_BYTES = 4096;

public

static void copyOnWrite() {

File file = new File("file/CopyTarget.txt");

file.delete();

FileUtil.createFile(file);

try (RandomAccessFile randomAccessFile =

new RandomAccessFile(file, "rw");){

FileChannel channel =

randomAccessFile.getChannel();

//init

channel.write(ByteBuffer.wrap("asd".getBytes()));

log.info("current channel size:{}", channel.size());

MappedByteBuffer copyOnWrite =

channel.map(FileChannel.MapMode.PRIVATE, 0, 10000);

MappedByteBuffer read =

channel.map(FileChannel.MapMode.READ_ONLY, 0, 10000);

MappedByteBuffer write =

channel.map(FileChannel.MapMode.READ_WRITE, 0, 10000);

log.info("current position:{}", copyOnWrite.position());

//copy on write的内存映射file

doWriteOnSameMemoryPage(copyOnWrite, read, write);

doWriteOnAnotherMemoryPage(copyOnWrite, read, write);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

private static void doWriteOnSameMemoryPage(MappedByteBuffer copyOnWrite, MappedByteBuffer read, MappedByteBuffer write) throws IOException {

log.info("write on the same page, so copy-on-write will not

update with writing---------");

copyOnWrite.put("h".getBytes());

printAll("copy on write 0-h", copyOnWrite, read, write);

//更改文件

write.position(6);

write.put("man".getBytes());

printAll("write 6-man", copyOnWrite, read, write);

}

private static void doWriteOnAnotherMemoryPage(MappedByteBuffer copyOnWrite, MappedByteBuffer read, MappedByteBuffer write) throws IOException {

log.info("Write on the another page, so copy-on-write will

update with writing---------");

//更改文件

write.position(MEMORY_PAGE_BYTES + 1);

write.put("heheda".getBytes());

printAll("write " + MEMORY_PAGE_BYTES + "-heheda", copyOnWrite, read, write);

}

private static void printAll(String prefix, MappedByteBuffer copyOnWrite, MappedByteBuffer read, MappedByteBuffer write) {

printBuffer("1. copyOnWrite " + prefix + ":", copyOnWrite);

printBuffer("2. read " + prefix + ":", read);

printBuffer("3. write " + prefix + ":", write);

System.out.println();

}

private static void printBuffer(String mode, ByteBuffer buffer) {

buffer.position(0);

System.out.print(mode + ":");

while (buffer.hasRemaining()) {

System.out.print((char) buffer.get());

}

System.out.println();

}

}

五、Channel-to-Channel数据传输

transferTo和transferFrom运行我们交叉连接两个channel,从而不再需要一个中间的buffer来传输数据。这两个方法只存在于FileChannel中,所以也就是说,必须有一个channel是FileChannel。另一个可以是socketChannel等其他WritableByteChannel和ReadableByteChannel。

直接的channel传输并不改变相关FileChannel的文件position。我们需要传输开始位置和传输数量,并返回实际传输的字节数。

对于transferTo,其数据源是FileChannel,如果position + count大于文件大小,那么也只是传输到文件末尾。如果目标是非阻塞socket,当发送队列(send queue)满后,传输会停止。并且如果输出队列(output queue)已满的话,可能不会发送任何数据。

对于transferFrom方法,其目的源是FileChannel,如果数据源是非阻塞socket,只有在传输队列中的数据才会传输。由于网络数据传输的非确定性,阻塞模式的socket 也可能会执行部分传输,这取决于操作系统。许多通道实现都是提供它们当前队列中已有的数据而不是等待您请求的全部数据都准备好。

Channel-to-channel传输可以非常快,特别是所依赖的操作系统具有底层支持的情况下。一些系统可以执行直接传输而不需要通过用户空间传递数据。对于大量数据传输这是巨大优势。

public static voidfileChannelTransferTo(FileChannel fileChannel,WritableByteChannel target)throwsIOException {

fileChannel.transferTo(0,fileChannel.size(),target);}public static voidfileChannelTransferFrom(FileChannel fileChannel,ReadableByteChannel source)throwsIOException {

fileChannel.transferFrom(source,0,fileChannel.size());}

@Testpublic voidtestFileChannelTransfer() {

File fileSource =newFile("file/CopySource.txt");File fileTarget =newFile("file/CopyTarget.txt");fileTarget.delete();FileUtil.createFile(fileTarget);

try(FileInputStream fileInputStream =newFileInputStream(fileSource);FileOutputStream fileOutputStream =newFileOutputStream(fileTarget);) {

FileChannel source = fileInputStream.getChannel();FileChannel target = fileOutputStream.getChannel();

longstartTime = System.currentTimeMillis();ChannelCopy.fileChannelTransferTo(source,target);log.info("fileChannelTransferTo cost : {}",System.currentTimeMillis() - startTime);source.close();target.close();}catch(FileNotFoundException e) {

e.printStackTrace();}catch(IOException e) {

e.printStackTrace();}

}

结论:可以发现,无论是channel.copy1还是copy2,都比stream copy快10倍以上。通过fileChannel-chanel transfer,又比直接的fileChannel快10倍以上。

对于拷贝6119kb的文件,Channel-chanel大概在7ms,copy1和2大概在80ms,而streamCopy 在1600ms

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值