前面讲了基本的IO,但是实际的项目中,文件大了之后一般会使用多线程来进行处理。本文主要是使用多线程对文件进行下载,并提供了进度显示。简单一点理解就是需要三个类,①一个类属于作业类,外界只需要告诉这个作业类输入流、输出流,当然本例主要讲多线程,那必然也需要传入待处理那段文件的起始位置,那么这个输出流必须使用RandomAccessFile来进行指定位置的写文件。②进度条处理类,由于进度条要实时更新,那么就需要使用java自带的Timer定时器,每隔1秒中读取所有线程的输入流中剩余的字节数,那么就能反算出此刻下载的进度。③作业和进度调用的测试类,这个类主要是分割文件,确认各个线程下载的起始位置,然后构建对应的输入输出流,再交给作业类来执行对应的任务。当任务分配完毕之后就可以启动定时器,每隔1秒去访问之前创建的输入流,进而确认下载进度。
主要包括三个类:DownThread、DownThreadClient、ShowDownLoadPercentTask。至于使用只需要将三个文件copy到自己的工作空间,DownThreadClient会有main函数来运行。
DownThread主要是控制多线程下载
DownThreadClient主要是测试下载任务、准备下载前的条件
ShowDownLoadPercentTask主要是显示下载进度
DownThread.java
package com.ds.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
public class DownThread extends Thread {
private final int BUFF_LEN = 1024;
private InputStream inputStream;
private RandomAccessFile raf;
private long start;
private long end;
private int flag = 1;
/**
* @param start 下载开始位置
* @param end 下载结束位置
* @param inputStream 输入流
* @param raf 输出流
* @param flag 第n个线程
*/
public DownThread(long start,long end,InputStream inputStream,RandomAccessFile raf,int flag){
this.start = start;
this.end = end;
this.inputStream = inputStream;
this.raf = raf;
this.flag = flag;
}
public void run(){
//System.out.println("Thread "+ flag +" start!");
try {
//初始化输入输出流的位置
inputStream.skip(start);
raf.seek(start);
byte[] buffer = new byte[BUFF_LEN];
long contentLen = end - start;
//设置读取界限,避免超过线程读取的文件分区范围
int times = (int)(contentLen/BUFF_LEN);
int hasRead = 0;
//根据读取界限读取文件
for(int i=0;i<=times;i++){
hasRead = inputStream.read(buffer);
if(hasRead == -1) break;
if(i==times){
raf.write(buffer, 0, (int)(contentLen%BUFF_LEN));
}else {
raf.write(buffer, 0, BUFF_LEN);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
//inputStream.close();暂时先别关闭输入流,计算下载进度时需要使用
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
DownThreadClient.java
package com.ds.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Timer;
import com.hundsun.jres.common.util.UUID;
public class DownThreadClient {
//默认启动4个线程
private int threadAccount = 4;
private String fileSavePath = "C:/Users/Administrator/Desktop/";
private InputStream[] inputStreams;
private RandomAccessFile[] rdfs;
private File file;
public int getThreadAccount() {
return threadAccount;
}
public InputStream[] getInputStreams() {
return inputStreams;
}
public File getFile() {
return file;
}
/**
* @param fileSavePath 新文件存储目录
* @param file 要下载的文件
*/
public DownThreadClient(String fileSavePath, File file) {
this.fileSavePath = fileSavePath;
this.file = file;
inputStreams = new InputStream[threadAccount];
rdfs = new RandomAccessFile[threadAccount];
}
/**
* 拼接出存储文件的绝对路径,文件名随机生成
* @param oldFileName 原始文件名
* @return
*/
public String getFilePath(String oldFileName){
//获取原始文件名的后缀
String suffix = oldFileName.substring(oldFileName.lastIndexOf("."));
UUID uuid = UUID.randomUUID();
String fileName = uuid.toString()+suffix;
return fileSavePath+fileName;
}
//开始下载任务
public void downLoad() throws Exception{
long fileLen = file.length();//文件总长度
long partLen = fileLen/threadAccount;//分区长度
String newFilePath = getFilePath(file.getName());//文件存储新路径
for(int i=0;i<threadAccount;i++){
long start = i* partLen;
long end = (i+1)*partLen;
//初始化输入输出流
inputStreams[i] = new FileInputStream(file);
rdfs[i] = new RandomAccessFile(newFilePath, "rw");
//如果是最后一段则设置下载结束位置为文件最末尾
if(i==threadAccount-1){
end = file.length();
}
//初始化并开启下载线程
new DownThread(start, end, inputStreams[i], rdfs[i], i).start();
}
}
/**
* 获取下载进度
* @param dtc DownThreadClient对象
*/
public void getDownLoadPercent(DownThreadClient dtc){
Timer timer = new Timer();
ShowDownLoadPercentTask sdlp = new ShowDownLoadPercentTask(dtc, timer);
//延迟1秒开启任务,每秒钟执行一次
timer.schedule(sdlp, 1000, 1000);
}
public static void main(String arg[]) throws Exception{
String filePath = "E:/ds.rmvb";
String fileSavePath = "C:/Users/Administrator/Desktop/";
File file = new File(filePath);
DownThreadClient dtc = new DownThreadClient(fileSavePath, file);
dtc.downLoad();
//显示下载进度
dtc.getDownLoadPercent(dtc);
}
}
ShowDownLoadPercentTask.java
package com.ds.io;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
public class ShowDownLoadPercentTask extends TimerTask{
private Timer timer;
private DownThreadClient dtc;
/**
* @param dtc DownThreadClient对象
* @param timer 定时器
*/
public ShowDownLoadPercentTask(DownThreadClient dtc, Timer timer) {
super();
this.dtc = dtc;
this.timer = timer;
}
//关闭输入流
public void closeIs(){
try {
for(int i=0; i<dtc.getThreadAccount(); i++){
dtc.getInputStreams()[i].close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
long currentLen = 0;
long totleLen = dtc.getFile().length();
try {
//计算已读取的字节数
for(int i=0; i<dtc.getThreadAccount(); i++){
//计算方式:已读长度=总长度-可读长度-跳过长度
currentLen += (totleLen - dtc.getInputStreams()[i].available()
-i*(totleLen/dtc.getThreadAccount()));
}
//获取下载进度
double percent = Math.ceil(currentLen*1.0/totleLen*10000);
System.out.println(percent/100.0+"%");
if(percent == 10000) {
//停止定时任务,关闭输入流,删除备份文件
timer.cancel();
closeIs();
System.out.println("下载完成");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}