FileChannel 中 transferFrom 和 transferTo 方法的區別你知道嗎


    關於FileChannel的解釋及用途網絡上的資料已經解釋的很清楚了, 總的概括來說 FileChannel 是 用於讀、寫、映射、維護一個文件的通道, 這里不再贅述

 

    起因

    FileChannel中提供了兩個方法 transferFrom(ReadableByteChannel src, long position, long count) transferTo(long position, long count, WritableByteChannel target)用於兩個通道間的數據傳輸,通常使用單線程進行傳輸的時候這兩個方法不會出現什么問題, 但是 當我使用多線程方式 進行文件的復制的時候, transferFrom 方法最后傳輸的總是 count的長度(count是每個線程平均分配處理的長度).  這個問題困擾我兩天,並且各處資料都沒有詳細的介紹關於這兩個方法在多線程情況下的問題, 在此做個記錄, 也希望后來者乘涼.

 

    問題復現▼

    以下代碼測試兩個方法使用多線程 copy文件

package com.example.day1nio;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BlogTest {


    public static void main(String[] args) {
        String url = "D:\\Study\\test.rar";
        String copyUrl = "D:\\Study\\copy.rar";

        try{
            File testFile = new File(url);
            long size = testFile.length();

            //定義每個線程處理的文件大小
            long subFileCount = 20 * 1024 * 1024;// 20M
            //定義線程池  控制同時處理的線程數 可以避免磁盤占用過高
            ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
            //計算需要多少線程處理
            long threadCount = size % subFileCount == 0 ? size/subFileCount : size/subFileCount +1;

            System.out.println("需要線程數:"+threadCount);

            CountDownLatch cdl = new CountDownLatch((int) threadCount);

            for(long i=0,position=0; i<threadCount; i++,position=i*subFileCount){
                if(size - position < subFileCount){
                    subFileCount = size - position;
                }
                fixedThreadPool.execute(new FileThread(url, copyUrl, position, subFileCount, cdl));
            }
            cdl.await();
            System.out.println(cdl.getCount());
            System.out.println("文件復制完畢");
            fixedThreadPool.shutdown();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    private static class FileThread extends Thread {

        private String fileUrl;
        private String copyUrl;
        private long position;
        private long count;
        private CountDownLatch cdl;

        public FileThread(String fileUrl, String copyUrl, long position, long count, CountDownLatch cdl) {
            this.fileUrl = fileUrl;
            this.copyUrl = copyUrl;
            this.position = position;
            this.count = count;
            this.cdl = cdl;
        }

        @Override
        public void run() {
            RandomAccessFile fileRaf = null;
            RandomAccessFile copyRaf = null;
            FileChannel fileChannel = null;
            FileChannel copyChannel = null;
            try {
                fileRaf = new RandomAccessFile(new File(fileUrl), "rw");
                copyRaf = new RandomAccessFile(new File(copyUrl), "rw");

                fileChannel = fileRaf.getChannel();
                copyChannel = copyRaf.getChannel();

                copyChannel.position(position);
                fileChannel.position(position);

                //使用兩個方法進行測試
//                fileChannel.transferTo(position, count, copyChannel);
                copyChannel.transferFrom(fileChannel, position, count);
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                cdl.countDown();
                closeStream(copyChannel, fileChannel, copyRaf, fileRaf);
            }
        }
    }

        //關閉流
        public static void closeStream(Closeable...closeables){
            for (Closeable closeable : closeables) {
                if (closeable != null){
                    try {
                        closeable.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

}

  

    使用transferFrom copy的文件▼

   

    使用transferTo copy的文件▼

   

    上面兩張圖片可以看出, 使用transferFrom copy的文件總是定義的每個線程分配的處理大小 20M, transferTo copy的文件是大小正常且沒有損壞, 具體原因往下看

 

    分析▼

    從方法名來看 transferTo 是將當前通道數據寫到另一個通道,  transferFrom 是從另一個通道拿數據到當前通道, 通過這個思路思考一下, 是什么原因呢?

    transferTo 是將當前通道數據寫到另一個通道, 對象是當前通道, 所以我們不用考慮另一個通道的什么, 我就把文件數據寫給你就行了, 而 transferFrom 是從另一個通道拿數據到當前通道, 在復制文件的情況下, 新建立的當前通道是空白的, 我們只知道要寫到哪個文件,但是不知道文件的大小, 所以默認每一次就從0開始寫入,  從而導致文件最后的大小是線程分配大小. 所以,在建立文件的同時,我們就給它指定一個大小, 然后每一次從指定的位置開始寫入, 那么就可以完整的復制一個文件了

    修改上面代碼中的 靜態內部線程類▼

    與以上代碼相比,只增加了一行代碼

//指定文件大小
copyRaf.setLength(fileRaf.length());

private static class FileThread extends Thread {

        private String fileUrl;
        private String copyUrl;
        private long position;
        private long count;
        private CountDownLatch cdl;

        public FileThread(String fileUrl, String copyUrl, long position, long count, CountDownLatch cdl) {
            this.fileUrl = fileUrl;
            this.copyUrl = copyUrl;
            this.position = position;
            this.count = count;
            this.cdl = cdl;
        }

        @Override
        public void run() {
            RandomAccessFile fileRaf = null;
            RandomAccessFile copyRaf = null;
            FileChannel fileChannel = null;
            FileChannel copyChannel = null;
            try {
                fileRaf = new RandomAccessFile(new File(fileUrl), "rw");
                copyRaf = new RandomAccessFile(new File(copyUrl), "rw");
                    
                //指定文件大小
                copyRaf.setLength(fileRaf.length());
                
                fileChannel = fileRaf.getChannel();
                copyChannel = copyRaf.getChannel();

                copyChannel.position(position);
                fileChannel.position(position);

                //使用兩個方法進行測試
                fileChannel.transferTo(position, count, copyChannel);
//                copyChannel.transferFrom(fileChannel, position, count);

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                cdl.countDown();
                closeStream(copyChannel, fileChannel, copyRaf, fileRaf);
            }
        }
    }

 

   歡迎關注我的微信公眾號  抓幾個娃  聊點技術,聊點生活

   

 

以上

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM