定义:FileChannel是Java NIO对应于磁盘等存储设备文件操作的通道。
一、获取FileChannel的API
/**
* 打开一个与文件的连接通道,用于进行文件操作。
* path:path,文件的路径对象,可用Path.get("文件路径"),获取。
* options:通道的操作参数。通常使用实现类枚举StandardOpenOption指定。
* attrs:创建文件时自动设置的文件属性的可选列表,比如读写执行。
*/
public static FileChannel open(Path path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs);
//打开一个与文件的连接通道,用于进行文件操作。并可以设置通道的操作参数options
public static FileChannel open(Path path, OpenOption... options)
demo:
public class FileChannelDemo {
public static void main(String[] args) throws IOException {
//以读和写的方式打开与文件555.txt的连接的通道。
FileChannel fileChannel = FileChannel.open(Paths.get("555.txt"),StandardOpenOption.READ,
StandardOpenOption.WRITE);
// StandardOpenOption是一个枚举类,代表着文件连接时的标准选项
Set<OpenOption> openOptions = new HashSet<>();
openOptions.add(StandardOpenOption.CREATE);
openOptions.add(StandardOpenOption.READ);
openOptions.add(StandardOpenOption.WRITE);
// PosixFilePermission是一个枚举类,代表着文件的基本权限。
Set<PosixFilePermission> fileAttributes = new HashSet<>();
fileAttributes.add(PosixFilePermission.OWNER_READ);
fileAttributes.add(PosixFilePermission.OWNER_WRITE);
fileAttributes.add(PosixFilePermission.OWNER_EXECUTE);
//以读和写和创建并打开与文件666.txt的连接的通道。并且创建的文件会赋予文件所属者读写执行的权限。
FileChannel fileChannel1 = FileChannel.open(Paths.get("666.txt"),openOptions,
PosixFilePermissions.asFileAttribute(fileAttributes));
}
}
二、读写API
//把文件数据通过通道读取到缓冲区dst中。返回读取到数据长度。返回-1表示到达文件尾。
public abstract int read(ByteBuffer dst) throws IOException;
//把文件数据通过通道读取到缓冲区数组dst中。返回读取到数据长度。返回-1表示到达文件尾。
//会依次读取到缓冲区数组中的缓冲区,知道把数据读完或者缓冲区不够大。
public final long read(ByteBuffer[] dsts) throws IOException;
//把文件数据通过通道读取到缓冲区数组dst中。返回读取到数据长度。返回-1表示到达文件尾。
//会依次读取到缓冲区数组中的缓冲区,知道把数据读完或者缓冲区不够大。
//可以设置读取到缓冲区数组中的哪些数组,假如dsts数组有5个缓冲区,offset=1 length=3,
//则只会把数据读取到缓冲区数组的下标 1,2,3三个缓冲区上,offset代表下标,length代表长度
public abstract long read(ByteBuffer[] dsts, int offset, int length);
//把文件数据通过通道读取到缓冲区数组dst中。
//以上的读取数组方法读取一个字节数据FileChannel中的游标会右移一位,但是这个方法不会移动游标。
//也就是重复读取还是读取那些数据。
public abstract int read(ByteBuffer dst, long position) throws IOException;
//把缓冲区中的position到limit-1的数据通过通道写到文件中去。返回写的数据长度。
public abstract int write(ByteBuffer src) throws IOException;
//把缓冲区数组中的多个缓冲区依次通过管道写到文件中去,offset与length与read对应方法大致。
public abstract long write(ByteBuffer[] srcs, int offset, int length)
//把缓冲区数组中的多个缓冲区依次通过管道写到文件中去。
public final long write(ByteBuffer[] srcs) throws IOException
//读取缓冲区的数组通过通道写到文件中去,可指定读取缓冲区的position偏移量。假如缓冲区当前
//position为0,position参数为1,那么最终缓冲区数据读取0+1 到limit-1 的数据。
public abstract int write(ByteBuffer src, long position) throws IOException;
demo:
public static void testRead() throws IOException {
FileChannel fileChannel = FileChannel.open(Paths.get("5555.txt"),StandardOpenOption.READ);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println("==================>>read(ByteBuffer dst)<<=====================");
//读取时从文件的总数据字节第二位开始读取
fileChannel.read(byteBuffer,1);
byteBuffer.flip();
System.out.println("position = " + fileChannel.position());
System.out.println(new String(byteBuffer.array()));
byteBuffer.clear();
System.out.println("==================>>read(ByteBuffer dst, long position)<<=====================");
fileChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println("position = " + fileChannel.position());
System.out.println(new String(byteBuffer.array()));
}
结果:第一个读取时使用read(ByteBuffer dst, long position)方法进行读取,position设置为1,所以文件的第一个字节数据I被跳过了。并且读完后通道的偏移量仍然为0。
三、MappedByteBuffer
//返回一个直接缓冲区MappedByteBuffer
//mode设置直接缓冲区的权限。
//position从通道的文件数据游标position开始。
//size表示要写到缓冲区的数据的长度
public abstract MappedByteBuffer map(MapMode mode, long position, long size)
throws IOException;
public static class MapMode {
/**
* 只读模式
*/
public static final MapMode READ_ONLY
= new MapMode("READ_ONLY");
/**
* 读写模式
*/
public static final MapMode READ_WRITE
= new MapMode("READ_WRITE");
/**
* 写时复制模式
*/
public static final MapMode PRIVATE
= new MapMode("PRIVATE");
private final String name;
private MapMode(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
使用MappedByteBuffer 实现文件复制:
public static void testMapMode() throws IOException {
FileChannel readFileChannel = FileChannel.open(Paths.get("test.txt"),StandardOpenOption.READ);
FileChannel writeFileChannel = FileChannel.open(Paths.get("test_copy.txt"),StandardOpenOption.READ,StandardOpenOption.WRITE,
StandardOpenOption.CREATE_NEW);
MappedByteBuffer readMap = readFileChannel.map(FileChannel.MapMode.READ_ONLY, 0, readFileChannel.size());
MappedByteBuffer writeMap = writeFileChannel.map(FileChannel.MapMode.READ_WRITE,0,readFileChannel.size());
byte[] array = new byte[readMap.remaining()];
readMap.get(array);
//无需通道直接进行文件写
writeMap.put(array);
writeFileChannel.close();
readFileChannel.close();
}
四、transferTo和transferFrom
/**
* 把本通道打开的文件的数据传输到目标通道target中。
* 可以指定本通道的position文件数据游标和要传输的数据大小size。如果position比本通道打开的文件大,就没有字节转移。
* 传输是使用直接内存以零拷贝的方式进行传输。
* 该传输不会改变本通道的position值。
*/
public abstract long transferTo(long position, long count, WritableByteChannel target)
throws IOException;
/**
* 把src通道打开的文件的数据传输到本通道中
* 可以指定转移写入的本通道的文件的position位置,和要转移的数据长度size。如果position比本通道打开的文件大,就没有字节转移。
* 传输是使用直接内存以零拷贝的方式进行传输。
* 该传输不会改变src通道的position值
*/
public abstract long transferFrom(ReadableByteChannel src,long position, long count)
throws IOException;
demo:
public static void testTransfer() throws IOException {
FileChannel readFileChannel = FileChannel.open(Paths.get("test.txt"),StandardOpenOption.READ);
//把文件用transferTo复制一份
FileChannel writeFileChannel = FileChannel.open(Paths.get("test_transferTo.txt"),StandardOpenOption.CREATE,StandardOpenOption.WRITE);
readFileChannel.transferTo(0,readFileChannel.size(),writeFileChannel);
// 把文件用transferFrom复制一份
FileChannel rfc = FileChannel.open(Paths.get("test_transferFrom.txt"),StandardOpenOption.CREATE,StandardOpenOption.WRITE);
rfc.transferFrom(readFileChannel,0,readFileChannel.size());
}
五、其他API
//关闭通道
public final void close();
//返回文件的大小
public abstract long size() throws IOException;
//返回此文件字节数据的偏移量。
public abstract long position();
//设置此文件字节数据的偏移量。如果设置地比文件大size大,就会在读取时读到文件尾。返回-1.
public abstract FileChannel position(long newPosition)
//强制将数据刷到存储设备上,如果metaData为false只刷数据,如果metaData为true,刷数据与元数据(权限等)
public abstract void force(boolean metaData);
//判断通道是否还打开着,没有关闭。
public final boolean isOpen();
六、FileChannel效率对比
package com.lxk.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.junit.Test;
/*
* 一、通道(Channel):用于源节点与目标节点的连接。在 Java NIO 中负责缓冲区中数据的传输。
* Channel 本身不存储数据,因此需要配合缓冲区进行传输。
*
* 二、通道的主要实现类
* java.nio.channels.Channel 接口:
* |--FileChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* 三、获取通道
* 1. Java 针对支持通道的类提供了 getChannel() 方法
* 本地 IO:
* FileInputStream/FileOutputStream
* RandomAccessFile
*
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
*
* 2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()
* 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()
*
* 四、通道之间的数据传输
* transferFrom()
* transferTo()
*/
public class TestChannel {
//利用通道完成文件的复制(非直接缓冲区)
@Test
public void test1() {//10874-10953
long start = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
//①获取通道
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("j:/1.avi");
fos = new FileOutputStream("j:/2.avi");
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//②分配指定大小的缓冲区--1K
ByteBuffer buf = ByteBuffer.allocate(1024); //1026
//ByteBuffer buf = ByteBuffer.allocateDirect(1024); //1010
//③将通道中的数据存入缓冲区中
while (inChannel.read(buf) != -1) {
buf.flip(); //切换读取数据的模式
//④将缓冲区中的数据写入通道中
outChannel.write(buf);
buf.clear(); //清空缓冲区
}
} catch (IOException 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 (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start)); // 1026
}
//使用直接缓冲区完成文件的复制(内存映射文件)
@Test
public void test2() throws IOException {
long start = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("j:/1.avi"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("j:/2.avi"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
// 注意:StandardOpenOption.CREATE_NEW 如果文件已存在,则报错:java.nio.file.FileAlreadyExistsException: j:\2.avi
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
// 注意:MapMode.READ_WRITE 要和 通道StandardOpenOption.WRITE, StandardOpenOption.READ 保持一直,
// 否则报错java.nio.channels.NonReadableChannelException
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));//146
}
//通道之间的数据传输(直接缓冲区)
@Test
public void test3() throws IOException {
long start = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("j:/1.avi"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("j:/2.avi"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
inChannel.transferTo(0, inChannel.size(), outChannel);
// outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));//72
}
}
附1:StandardOpenOption枚举类
代表着文件连接时的标准选项,通常可以多个选项一起使用。
public enum StandardOpenOption implements OpenOption {
/**
* 以读的方式连接文件。
*/
READ,
/**
* 以写的方式连接文件。
*/
WRITE,
/**
* 以追加的方式连接文件,不会覆盖文件原本内容,在后面追加。
*/
APPEND,
/**
* 如果文件存在并且以WRITE的方式连接时就会把文件内容清空,文件设置为0字节大小。
* 如果文件只以READ连接 时,该选项会被忽略。
*/
TRUNCATE_EXISTING,
/**
* 创建一个文件,如果文件已存在,就打开文件连接。与CREATE_NEW同时存在时该选项会被忽略。
*/
CREATE,
/**
* 创建一个文件,如果文件已存在,如果已经存在会抛异常。
*/
CREATE_NEW,
/**
* 通道关闭时删除文件
*/
DELETE_ON_CLOSE,
/**
* 创建稀疏文件,与CREATE_NEW选项配合使用。
*/
SPARSE,
/**
* 要求每次写入要把内容和元数据刷到存储设备上。
*/
SYNC,
/**
* 要求每次写入那内容刷到存储设备上
*/
DSYNC;
}
附2:PosixFilePermission枚举类
代表着文件的基本权限。
public enum PosixFilePermission {
/**
* 文件所有者的读权限
*/
OWNER_READ,
/**
* 文件所有者写权限。
*/
OWNER_WRITE,
/**
* 文件所有者执行权限
*/
OWNER_EXECUTE,
/**
* 文件所属组成员读权限
*/
GROUP_READ,
/**
* 文件所属组成员写权限
*/
GROUP_WRITE,
/**
* 文件所属组成员执行权限
*/
GROUP_EXECUTE,
/**
* 其他账号读权限。
*/
OTHERS_READ,
/**
* 其他账号写权限。
*/
OTHERS_WRITE,
/**
* 其他账号执行权限。
*/
OTHERS_EXECUTE;
}