多线程断点下载工具需要完成以下几个功能:
* 1.通过提供的网络文件地址,下载該文件
* 2.要求使用多个线程同时下载,并且实时更新下载进度(0%~100%)
* 3.要求能够实现断点功能(扩展)
实现思想:
1.创建多个下载线程和一个进度线程,利用线程名字区分两类线程。以下内容以四个下载线程为例。
2.根据下载链接构建一个URL对象,根据此URL对象打开一个HttpURLConnection链接对象conn,然后调用conn.getContentLength()获得下载文件的大小。
3.将文件大小平分四份设为每个线程的下载任务,并且记录每个文件下载的起始和终止位置,该量为long类型。
4.重写线程类的run方法,添加switch函数以区分下载线程和查看进度线程的任务,其中线程名字为switch的参数。
5.下载文件时要先调用readPosition函数来查看文件当前下载位置,此处的查看地点是自己创建的txt文件,txt文件的内容是实时跟新的当前已下载部分的最末尾。然后线程开始自己的下载任务。读文件采用BufferedInputStream流,因为HttpURLConnection.getInputStream()的参数为字节流,所以用BufferedInputStream高级字节流来包装被下载文件的字节内容。写文件采用RandomAccessFile流,因为需要完成断点拷贝任务,而RandomAccessFile流可以调用seek()函数来完成移动指针的目的,从而完成在指定位置开始写。线程完成了当前读写任务后,则需要调用writePosition函数将自己当前的读写末尾位置写入一个txt文件中,以实现断点下载 的目的,即下一次下载时线程能读到此次写入的末尾,从而从上一次的末尾继续往后下载,所以每次下载时要调用readPosition函数来查看文件当前下载位置。
6.其次是进度函数。进度函数会一直查看已下载的大小,和文件总大小进行对比得到文件下载进度。
以下是运行效果:
首次执行:
终止程序,再次执行后,完成断点任务:
完成 下载:
记录文件长度的len文本:
以下是源代码:
package HomeWork;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
public class DownloadFromNet extends Thread{
/**
* 完成一个多线程断点下载工具
* 1.通过提供的网络文件地址,下载該文件
* 2.要求使用多个线程同时下载,并且实时更新下载进度(0%~100%)
* 3.要求能够实现断点功能(扩展)
* @throws IOException
*/
private boolean isExit = false; // 控制进度线程,下载进度100%则赋值true,线程终止
private String s; // 下载文件地址 http
private File dir; // 下载路径
private long start; // 进程开始下载下标
private long end; // 进程结束下载下标
private long totalLength; // 下载文件的大小
private int i; // 进程ID
public DownloadFromNet(){
}
public DownloadFromNet(String s, long totalLength, String name){
super(name);
this.s = s;
this.totalLength = totalLength;
}
public DownloadFromNet(String s, File dir , long totalLength, long start, long end, String name, int i) {
super(name);
this.s = s;
this.dir = dir;
this.totalLength = totalLength;
this.start = start;
this.end = end;
this.i = i;
}
@Override
public void run() {
switch(this.getName()){ // 根据线程名字进行分配
case "下载":
try {
download();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
break;
case "进度":
progress();
break;
}
}
public void download() throws IOException, InterruptedException{
HttpURLConnection conn = creatConn(s);// 重新连接 不要一直用一个 如 主函数传conn进来
RandomAccessFile raf_write = null;
BufferedInputStream bis = null;
long length = 0;
//读
//获取响应的状态码
int code = conn.getResponseCode();
//判断是否响应成功
if(code == HttpURLConnection.HTTP_OK){
if (!new File(dir, "len.txt").exists())// 在这里判断是不是存在 不存在在这里读一次 调用writePosition
for (int i = 0; i < 4; ++i)
writePosition(0L, i);
length = readPosition(i);
if (i > 1 && length == 0L) // 如果是下载线程是第一次执行且下载任务不是下载文件首端的,则为其赋初始值start
length = start;
// 开启读
InputStream is = conn.getInputStream();
bis = new BufferedInputStream(is);
//准备目标文件
File f = new File("C:\\Users\\admin\\Desktop",s.substring(s.lastIndexOf("/")));
//以读写模式开启一个文件访问流
raf_write = new RandomAccessFile(f, "rw");
//读写指针跳到指定位置
bis.skip(length);
raf_write.seek(length);
byte[] b = new byte[1024];// 1k
int len = 0;
for(int i = 0;((len = bis.read(b)) != -1 && length <= end); i++)
{
sleep(100);
length += len;
raf_write.write(b, 0, len);
writePosition(length, i);
}
raf_write.close();
bis.close();
}
}
public long readPosition(int i) throws IOException{
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(new File("C:\\Users\\admin\\Desktop\\Test\\len.txt"), "rw");
raf.seek(8 * i);
System.out.println(raf.readLong());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally{
try {
if(raf != null)raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return 0L;
}
/**
* 实时记录文件拷贝(下载)到的目标位置
* @param position
* @param dir
*/
public void writePosition(long position, int i){ // position文件大小, i是线程数
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(new File(dir,"len.txt"),"rw");
raf.seek(8 * i); // 每个线程存储position的大小为八个字节
raf.writeLong(position);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(raf != null)raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void progress(){ // 输出下载进度函数
DecimalFormat bd = new DecimalFormat("##.##%");
RandomAccessFile raf_read2 = null;
long Length2;
long Length3;
while (!isExit){
try {
File file = new File("C:\\Users\\admin\\Desktop", s.substring(s.lastIndexOf("/")));
raf_read2 = new RandomAccessFile(file, "rw");
Length2 = raf_read2.length();
Length3 = totalLength;
if (Length2 == Length3) isExit = true;
System.out.println("进度:" + bd.format(1.0 * Length2 / Length3));
try {
sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (raf_read2 != null)
raf_read2.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("下载完成");
}
public static void main(String[] args) throws IOException {
String s = "http://dynamic-image.yesky.com/740x-/uploadImages/2016/337/54/492907OO7E5I.jpg";
HttpURLConnection conn = DownloadFromNet.creatConn(s);
long len = conn.getContentLength();
File dir = new File("C:\\Users\\admin\\Desktop");
long position2 = len / 4;
long position3 = position2 * 2;
long position4 = position2 * 3;
DownloadFromNet dfn1 = new DownloadFromNet(s, dir, len, 0L, position2, "下载", 0);
DownloadFromNet dfn2 = new DownloadFromNet(s, dir, len, position2, position3, "下载", 1);
DownloadFromNet dfn3 = new DownloadFromNet(s, dir, len, position3, position4, "下载", 2);
DownloadFromNet dfn4 = new DownloadFromNet(s, dir, len, position4, len, "下载", 3);
DownloadFromNet dfn5 = new DownloadFromNet(s, len, "进度");
dfn1.start();
dfn2.start();
dfn3.start();
dfn4.start();
dfn5.start();
}
private static HttpURLConnection creatConn(String string) throws IOException {
//根据字符串构建一个URL对象
URL url = new URL(string);
//打开一个URL链接
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
//设置请求方法(get请求)
conn.setRequestMethod("GET");
//设置读取超时时间
conn.setReadTimeout(10000);
return conn;
}
}