• 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

  • 相关阅读:
    Android开发系列之ListView用法
    自省
    fake feeling ?
    我心中的天使
    2020.6.16
    python面试题
    tcp
    Short + skip + limit
    01_pyttsx3_将文本文字转为语音播放
    mongo_Linux下不进入数据库查数据
  • 原文地址:https://www.cnblogs.com/hkdpp/p/8422761.html
Copyright © 2020-2023  润新知