業務描述
復制某目錄下的一個大文件,要求使用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