zoukankan      html  css  js  c++  java
  • NIO之FileChannel类的理解和使用

    文章链接:http://blog.csdn.net/qq_16628781/article/details/70532307

    知识点:

    1. FileChannel类及方法理解;
    2. 普通输入输出流复制文件;
    3. FileChannel复制文件;
    4. 新名词记录:{MappedByteBuffer:文件映射在内存的直接换成字节数据;FileLock:代表文件的锁;ByteBuffer:缓存对象}

    概述

    对于文件的复制,平时我们都是使用输入输出流进行操作,利用源文件创建出一个输入流,然后利用目标文件创建出一个输出流,最后将输入流的数据读取写入到输出流中。这样也是可以进行操作的。但是利用fileChannel是很有用的一个方式。它能直接连接输入输出流的文件通道,将数据直接写入到目标文件中去。而且效率更高。

    FileChannel类

    FileChannel是一个用读写,映射和操作一个文件的通道。出了读写操作之外,还有裁剪特定大小文件truncate()强制在内存中的数据刷新到硬盘中去force()对通道上锁lock()等功能。

    他们的使用分别如下面代码:

    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    //读取1024字节内容到byteBuffer钟
    fileChannelInput.read(byteBuffer);

    解释:上面代码首先创建一个1024大小的缓冲对象,然后在输入通道中读取1024大小数据,放入到缓冲对象中。

    byteBuffer.clear();
    byteBuffer.put("需要写入的数据".getBytes());
    //类似于flush()函数功能,将buffer里面的数据刷新出去
    byteBuffer.flip();
    //检查是否还有数据未写入
    while (byteBuffer.hasRemaining()) fileChannelOutput.write(byteBuffer);

    解释:上面的代码是将一段字符串写入到输出文件通道中,因为写入的时候并不保证能一次性写入到文件中,所以需要进行判断是否全部写入,如果没有需要再次调用写入函数操作

    //获取文件通道位置
    fileChannelInput.position();
    fileChannelInput.size();
    //截取内容
    fileChannelInput.truncate(1024);
    //强制刷新数据到硬盘
    fileChannelInput.force(true);

    解释:上面的代码是获取文件通道的位置和大小。truncate()方法是截取1024大小的数据,指定长度后面的部分将被删除。以及将数据强制刷新到硬盘中,因为系统会将数据先保存在内存中,不保证数据会立即写入到硬盘中,所以有这个需求,就可以直接强制数据写入内存中。


    使用

    说那么多可能没用,我们还是直接来看看分别使用两种方法进行文件复制的对比。

    首先是普通的输入输出流进行复制文件:

    /**
         * 普通的文件复制方法
         *
         * @param fromFile 源文件
         * @param toFile   目标文件
         * @throws FileNotFoundException 未找到文件异常
         */
        public void fileCopyNormal(File fromFile, File toFile) throws FileNotFoundException {
            InputStream inputStream = null;
            OutputStream outputStream = null;
            try {
                inputStream = new BufferedInputStream(new FileInputStream(fromFile));
                outputStream = new BufferedOutputStream(new FileOutputStream(toFile));
                byte[] bytes = new byte[1024];
                int i;
                //读取到输入流数据,然后写入到输出流中去,实现复制
                while ((i = inputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, i);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null)
                        inputStream.close();
                    if (outputStream != null)
                        outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    在上面的代码中,传入源文件和目标文件两个参数,然后根据两个文件,分别出具输入输出流,然后将输入流的数据读取,并且写入输出流中,就完成了文件的复制操作。

    下面再看一下利用fileChannel进行文件的复制操作。

    /**
         * 用filechannel进行文件复制
         *
         * @param fromFile 源文件
         * @param toFile   目标文件
         */
        public void fileCopyWithFileChannel(File fromFile, File toFile) {
            FileInputStream fileInputStream = null;
            FileOutputStream fileOutputStream = null;
            FileChannel fileChannelInput = null;
            FileChannel fileChannelOutput = null;
            try {
                fileInputStream = new FileInputStream(fromFile);
                fileOutputStream = new FileOutputStream(toFile);
                //得到fileInputStream的文件通道
                fileChannelInput = fileInputStream.getChannel();
                //得到fileOutputStream的文件通道
                fileChannelOutput = fileOutputStream.getChannel();
                //将fileChannelInput通道的数据,写入到fileChannelOutput通道
                fileChannelInput.transferTo(0, fileChannelInput.size(), fileChannelOutput);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fileInputStream != null)
                        fileInputStream.close();
                    if (fileChannelInput != null)
                        fileChannelInput.close();
                    if (fileOutputStream != null)
                        fileOutputStream.close();
                    if (fileChannelOutput != null)
                        fileChannelOutput.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    上面代码中,也是先分别创建了两个文件的输入输出流,然后在分别获取到两个文件的文件通道,然后将源文件的文件通道直接和目标文件的文件通道进行连接,直接将数据写入到目标文件中区。不需要进行分别的读取和写入操作了。

    运行代码之后,复制一个文件,对比两种复制方法,发现利用filechannel使用的时间比普通的读取输入时间缩短了将近一半。尤其是在进行大文件复制的时候,filechannel显得更加有优势。

    public static void main(String[] args) {
            try {
                long begintime = System.currentTimeMillis();
                System.out.println(begintime);
    //            fileCopyNormal(new File("f:\CentOS-7-x86_64-DVD-1511.iso"), new File("g:\tttttt.iso"));
                fileCopyWithFileChannel(new File("f:\CentOS-7-x86_64-DVD-1511.iso"), new File("g:\tttttt.iso"));
                long endtime = System.currentTimeMillis();
                System.out.println(endtime);
                System.out.println(TimeUnit.MILLISECONDS.toMinutes(endtime-begintime));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    CentOS-7-x86_64-DVD-1511.iso是一个4.03G大小的镜像文件,可以看到两次复制时间正好相差一倍:

    1517725850901
    1517726016732
    2

    1517726138039
    1517726215647
    1


    文件加锁

    JDK1.4引入了文件加锁机制。它允许我们同步访问某个作为共享资源的文件。不过,竞争同一文件的两个线程可能在不同的Java虚拟机上,或者一个是Java线程,另一个是操作系统中的其他的某个本地线程文件加锁对其他的操作系统进程是可见的,因为Java的文件加锁直接映射到了本地操作系统的枷锁工具。

          通过对FileChannel调用tryLock()或者lock(),就可以获得整个文件的FileLock。其中tryLock()是非阻塞式的,它设法获得锁,如果不能获得(其他的一些进程已经持有相同的锁,并且不共享),它将直接从方法调用返回。lock()则是阻塞式的,它要阻塞进程直至锁可以获得,或者调用lock()的线程中断(就是自己先挂掉了),或调用lock()的通道关闭。使用FileLock.release()可以释放锁。 

    FileOutputStream fos = new FileOutputStream(new File("lock.tct"));
    
    FileLock fl = fos.getChannel().tryLock();
    
    if(fl!=null) {
    
        XXXX//各种文件操作
    
        fl.release(); //释放锁
    
    }
    
    fos.close();

    也可以通过如下方法对文件的一部分上锁

     tryLock(long pos , long size , boolean shared) 
     lock(long pos , long size , boolean shared) 
    //加锁的区域由size-position决定。第三个参数指定是否是共享锁

            无参数的加锁方法将根据文件尺寸的变化而变化,而固定尺寸的锁不随文件尺寸的变化而变化。如果你在某一区域上加锁了,那么当文件增大的时候,加锁区域还是那个区域,多出的区域不会被锁定。而无参数的时候,是对整个文件进行加锁,文件变大后,也是对整个文件进行加锁。

    更多文件锁的知识:JAVA NIO 文件锁FileLock

    总结

    这里我们了解了FileChannel类,知道了它所具有的特点和功能,那么我们就可以好好的使用它了。尤其是在我们复制文件的时候,可以更好的利用这个类,可以提高效率,也可以防止出现oom等其它情况。

    以上就是所有内容,如有任何问题,请及时与我联系。

  • 相关阅读:
    BZOJ 3282: Tree( LCT )
    BZOJ 3713: [PA2014]Iloczyn( 枚举 )
    HDU3974
    CodeForces220B
    POJ2349
    HDU3038
    POJ1611
    IELTS
    POJ1125
    POJ2109
  • 原文地址:https://www.cnblogs.com/shamo89/p/8413177.html
Copyright © 2011-2022 走看看