业务描述
复制某目录下的一个大文件,要求使用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