Java利用多線程復制文件

前言

復制一個文件,是學習IO流時最基本的操作。你可以使用字節型文件流,也可以使用高級緩沖流。

但是,它們都是單線程的。

如果需要復制一個大型文件,單線程的復制一般而言是不能夠充分發揮CPU以及內存的性能。這時候就需要利用多線程來復制文件。

多線程的讀:

我們很自然地想到,利用FileInputStream類的skip()方法,可以跳著讀,這就對多線程比較友好,啟動多個線程,第一個線程讀一部分,第二個線程跳過一部分字節再讀,這沒有問題。

但是如果要寫呢?我們知道FileOutputStream類是沒有與skip類似的方法的,也就是說,它不能跳著寫,這就很麻煩。

這就意味著,如果需要利用多線程復制一個文件,那麼首先得把這個文件利用多線程並發,讀取並同時寫入成多個文件碎片,然後再利用單線程去一一讀取這些文件碎片,把它們的內容寫入到一個完整的文件中。必要的話,最後再刪除這些中間文件碎片。

這個過程,復雜,且性能不高。涉及到兩套讀寫,前面多線程讀寫,後面單線程讀寫。很顯然這個過程雖然使用到瞭多線程但它不能提高性能,反而降低瞭性能。

既然雞肋點在於FileOutputStream不能跳著寫,那麼就找一個能跳著寫的類吧。

RandomAccessFile

RandomAccessFile是java Io體系中功能最豐富的文件內容訪問類。即可以讀取文件內容,也可以向文件中寫入內容。但是和其他輸入/輸入流不同的是,程序可以直接跳到文件的任意位置來讀寫數據。

RandomAccessFile包含瞭以下兩個方法來操作文件的記錄指針:

  • long getFilePointer(); 返回文件記錄指針的當前位置
  • void seek(long pos); 將文件記錄指針定位到pos位置

有瞭RandomAccessFile這個類,上面的問題就迎刃而解,因為它最大的好處就是可以實現從指定位置寫入一些數據到文件中!

順便說一下它的構造方法:

  • RandomAccessFile(File file, String mode)
  • RandomAccessFile(String name, String mode)

mode表示RandomAccessFile的訪問模式,她有4個值:

  • “r”:隻有讀的權限,如果你試圖去執行寫入方法,則拋出IOException異常。
  • “rw”:有讀和寫 兩個權限,如果該文件不存在,則會創建該文件。
  • “rws”:相對於”rw” 模式,還要求對文件內容或元數據的每個更新都同步寫入到底層設備。
  • “rwd”:相對於”rw” 模式,還要求對文件內容每個更新都同步寫入到底層設備。

一般而言我們使用“r”和“rw”就夠瞭,後面那兩個我也不懂幹嘛的。

代碼

package testThread.file_threading;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

public class ThreadFileCopy extends Thread {
    private String srcFileStr;//源文件的路徑
    private String desFileStr;//目標文件的路徑 ,des --> destination目的地
    private long skipLen;//跳過多少個字節開始讀/寫
    private long workload;//總共要讀/寫多少個字節
    private final int IO_UNIT = 1024;//每次讀寫的基本單位(1024個字節)

    public ThreadFileCopy(String srcFileStr, String desFileStr, long skipLen, long workload) {
        this.srcFileStr = srcFileStr;
        this.desFileStr = desFileStr;
        this.skipLen = skipLen;
        this.workload = workload;
    }
    public void run(){
        FileInputStream fis = null;
        BufferedInputStream bis = null;//利用高級緩沖流,加快讀的速度
        RandomAccessFile raf = null;
        try {
            fis = new FileInputStream(srcFileStr);
            bis = new BufferedInputStream(fis);
            raf = new RandomAccessFile(desFileStr,"rw");
            bis.skip(this.skipLen);//跳過一部分字節開始讀
            raf.seek(this.skipLen);//跳過一部分字節開始寫
            byte[] bytes = new byte[IO_UNIT];
            //根據總共需要復制的字節數 和 讀寫的基本單元 計算出一共需要讀寫的次數,利用讀寫次數控制循環
            long io_num = this.workload/IO_UNIT + 1;//因為workload/1024 很可能不能整除,會有餘數
            if(this.workload % IO_UNIT == 0)
                io_num--;//如果碰巧整除,讀寫次數減一
            //count表示讀取的有效字節數,雖然count不參與控制循環結束,
            // 但是它能有效避免最後一次讀取出的byte數組中有大量空字節寫入到文件中,導致復制出的文件稍稍變大
            int count = bis.read(bytes);
            while (io_num != 0){
                raf.write(bytes,0,count);
                count = bis.read(bytes,0,count);//重新計算count的值
                io_num--;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bis != null)
                    bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (raf != null)
                    raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

測試

package testThread.file_threading;

import java.io.File;

public class TestMain {
    public static void main(String[] args) {
        int thread_num = 5;//創建5個線程讀寫
        String srcFileStr = "E:\\test\\123.flv";
        String desFileStr = "E:\\test\\thread.flv";
        File srcFile = new File(srcFileStr);
        long workload = srcFile.length()/thread_num;//總共要讀/寫多少個字節
        //用一個數組來存儲每個線程跳過的字節數
        long[] skipLenArr = new long[thread_num];
        for(int i = 0;i<skipLenArr.length;i++){
            skipLenArr[i] = i*workload;
        }
        //用一個數組來存儲所有的線程
        ThreadFileCopy[] tfcs = new ThreadFileCopy[thread_num];
        //初始化所有線程
        for(int i = 0;i<tfcs.length;i++){
            tfcs[i] = new ThreadFileCopy(srcFileStr,desFileStr,skipLenArr[i],workload);
        }
        //讓所有線程進入就緒狀態
        for(int i = 0;i<tfcs.length;i++){
            tfcs[i].start();
        }
        System.out.println("復制完畢!");
    }
}

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: