java多線程實現復制功能並統計進度


業務描述

復制某目錄下的一個大文件,要求使用10個線程同時工作。並且統計復制的完成度,類似於進度條的功能。

業務分析

步驟:

1、在使用多線程進行拷貝的時候,首先要知道文件的大小 然后根據線程的數量,計算出每個線程的工作的數量。需要一個拷貝的類,進行復制,初始化線程數組

2、創建一個統計文件復制進度的線程類。

3、拷貝線程。

4、由於Java的簡單類型不能夠精確的對浮點數進行運算,提供一個java工具類,對浮點數進行計算。

5、創建主函數類進行測試。

代碼如下:

package javasimple;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigDecimal;

public class ThreadCopyFile {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Copyer copyer = new Copyer();
        copyer.copy(new File("E:\\gcfr\\hqreportnew.war"), "E:\\", 10);
    }
}
/**
 * 該類執行文件的拷貝功能
 * @author haokui
 *
 */
class Copyer {

    private CopyThread[] threads;// 存放所有拷貝線程的數組

    /**
     * 使用多線程去拷貝一個大文件, 1 在使用多線程進行拷貝的時候,首先要知道文件的大小 然后根據線程的數量,計算出每個線程的工作的數量
     * 2.然后創建線程,執行拷貝的工作
     * 
     * @param scrFile
     *            源文件
     * @param desPath
     *            目標路徑
     * @param threadNum
     *            要使用的線程數量
     */
    public  void copy(File srcFile, String desPath, int threadNum) {
        // 1.取得文件的大小
        long fileLeng = srcFile.length();
        System.out.println("文件大小:" + fileLeng);

        // 2.根據線程數量,計算每個線程的工作量
        long threadPerSize = fileLeng / threadNum;

        // 3.計算出每個線程的開始位置和結束位置
        long startPos = 0;
        long endPos = threadPerSize;

        // 取得目標文件的文件名信息
        String fileName = srcFile.getName();
        String desPathAndFileName = desPath + File.separator + fileName;

        // 初始化線程的數組
        threads = new CopyThread[threadNum];

        for (int i = 0; i < threadNum; i++) {
            // 由最后一個線程承擔剩余的工作量
            if (i == threadNum - 1) {
                threads[i] = new CopyThread("拷貝線程" + i, srcFile,
                        desPathAndFileName, startPos, fileLeng);
            } else {
                // 創建一個線程
                threads[i] = new CopyThread("拷貝線程" + i, srcFile,
                        desPathAndFileName, startPos, endPos);
            }
            startPos += threadPerSize;
            endPos += threadPerSize;
            
        }

        // 創建統計線程
        new ScheduleThread("統計線程", fileLeng,threads );
    }
}
/**
 * 負責統計文件拷貝進度的線程
 * @author haokui
 *
 */
class ScheduleThread extends Thread {
    private long fileLength; // 文件的大小
    private CopyThread[] threads;// 存放所有的拷貝線程的數組

    /**
     * 統計進度線程的構造方法
     * 
     * @param name
     *            線程的名字
     * @param fileLeng
     *            文件的長度
     * @param threads
     *            拷貝線程的數組
     */
    public ScheduleThread(String name, long fileLength, CopyThread[] threads) {
        super(name);
        this.fileLength = fileLength;
        this.threads = threads;

        this.start();
    }

    /**
     * 判斷所有的拷貝線程是否已經結束
     * 
     * @return 是否結束
     */
    private boolean isOver() {
        if (threads != null) {
            for (CopyThread t : threads) {
                if (t.isAlive()) {
                    return false;
                }
            }
        }
        return true;
    }

    public  void run() {
        while (!isOver()) {
            long totalSize = 0;
            for (CopyThread t : threads) {
                totalSize += t.getCopyedSize();
            }
            /**
             * 由於復制功能要比這些代碼耗時,所以稍微延遲一下,不用計算的太頻繁,最好是一個線程干完之后計算一次,這里就直接給延遲一下就ok,不做精確的處理了。
             */
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            double schedule = Arith.div((double) totalSize,
                    (double) fileLength, 4);
            System.err.println("文件的拷貝進度:===============>" + schedule * 100
                    + "%");
        }
        System.err.println("統計線程結束了");
    }
}
/**
 * 拷貝線程
 * @author haokui
 *
 */
class CopyThread extends Thread {
    private File srcFile;// 源文件的路徑
    private String desPath;// 目標路徑
    private long startPos; // 線程拷貝的開始位置
    private long endPost;// 線程拷貝的結束位置
    private long alreadyCopySize;// 線程已經拷貝的位置

    private RandomAccessFile rin; // 讀取文件的隨機流
    private RandomAccessFile rout;// 寫入文件的隨機流

    /**
     * 取得 線程已經拷貝文件的大小
     * 
     * @return 線程已經拷貝文件的大小
     */
    public long getCopyedSize() {
        return alreadyCopySize - startPos;
    }

    /**
     * 線程的構造方法
     * 
     * @param threadName
     *            線程的名字
     * @param scrFile
     *            源文件
     * @param desPathAndName
     *            目標文件的路徑及其名稱
     * @param startPos
     *            線程的開始位置
     * @param endPost
     *            線程的結束位置
     */
    public CopyThread(String threadName, File srcFile, String desPathAndName,
            long startPos, long endPos) {
        super(threadName);
        this.srcFile = srcFile;
        this.desPath = desPath;
        this.startPos = startPos;
        this.endPost = endPos;
        this.alreadyCopySize = this.startPos;

        // System.out.println(this.getName() + "開始位置:" + startPos + " 結束位置:"
        // + endPos);

        // 初始化隨機輸入流,輸出流
        try {
            rin = new RandomAccessFile(srcFile, "r");
            rout = new RandomAccessFile(desPathAndName, "rw");

            // 定位隨機流的開始位置
            rin.seek(startPos);
            rout.seek(startPos);

            // 開始線程
            this.start();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public  void run() {
        int len = 0;
        byte[] b = new byte[1024];

        try {
            while ((alreadyCopySize < endPost) && (len = rin.read(b)) != -1) {
                alreadyCopySize = alreadyCopySize + len;
                if (alreadyCopySize >= this.endPost) {
                    int oldSize = (int) (alreadyCopySize - len);
                    len = (int) (this.endPost - oldSize);
                    alreadyCopySize = oldSize + len;
                }
                rout.write(b, 0, len);
            }
            System.out.println(this.getName() + " 在工作: 開始位置:" + this.startPos
                    + "  拷貝了:" + (this.endPost - this.startPos)  + " 結束位置:"
                    + this.endPost);
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rin != null) {
                    rin.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (rout != null) {
                    rout.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

/**
 * 由於Java的簡單類型不能夠精確的對浮點數進行運算,
 * 這個工具類提供精 確的浮點數運算,包括加減乘除和四舍五入。
 * @author haokui
 *
 */
class Arith {
    // 默認除法運算精度
    private static final int DEF_DIV_SCALE = 10;

    // 這個類不能實例化
    private Arith() {
    }

    /**
     * 提供精確的加法運算。
     * 
     * @param v1
     *            被加數
     * @param v2
     *            加數
     * @return 兩個參數的和
     */
    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

    /**
     * 提供精確的減法運算。
     * 
     * @param v1
     *            被減數
     * @param v2
     *            減數
     * @return 兩個參數的差
     */
    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 提供精確的乘法運算。
     * 
     * @param v1
     *            被乘數
     * @param v2
     *            乘數
     * @return 兩個參數的積
     */
    public static double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供(相對)精確的除法運算,當發生除不盡的情況時,精確到 小數點以后10位,以后的數字四舍五入。
     * 
     * @param v1
     *            被除數
     * @param v2
     *            除數
     * @return 兩個參數的商
     */
    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指 定精度,以后的數字四舍五入。
     * 
     * @param v1
     *            被除數
     * @param v2
     *            除數
     * @param scale
     *            表示表示需要精確到小數點以后幾位。
     * @return 兩個參數的商
     */
    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精確的小數位四舍五入處理。
     * 
     * @param v
     *            需要四舍五入的數字
     * @param scale
     *            小數點后保留幾位
     * @return 四舍五入后的結果
     */
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
};

假設復制e:/gcfr下的一個war包到e盤根目錄下。運行結果如下:

 

 注意:10個線程同時工作,輸出的順序不一樣正式體現。進度最后不是100%是因為統計的時候加了個延時,要看最后一個線程的結束位置,如果和文件的大小相等,表示就復制成功,沒有字節丟失。此文件的大小是30995468


免責聲明!

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



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