java多线程实现复制功能并统计进度

Wesley13
• 阅读 564

业务描述

复制某目录下的一个大文件,要求使用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盘根目录下。运行结果如下:

java多线程实现复制功能并统计进度

 注意:10个线程同时工作,输出的顺序不一样正式体现。进度最后不是100%是因为统计的时候加了个延时,要看最后一个线程的结束位置,如果和文件的大小相等,表示就复制成功,没有字节丢失。此文件的大小是30995468

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java 复制Map对象(深拷贝与浅拷贝)
java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这