Java NIO之通道FileChannel详解

定义: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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值