java 多线程下载 断点_Java 多线程断点下载文件

基起原根蒂根基理:哄骗URLConnection获取要下载文件的长度、头部等相干信息,并设置响应的头部信息。并且经由过程URLConnection获取输入流,将文件分成指定的块,每一块零丁开辟一个线程完成数据的读取、写入。经由过程输入流读取下载文件的信息,然后将读取的信息用RandomAccessFile随机写入到本地文件中。同时,每个线程写入的数据都文件指针也就是写入数据的长度,须要保存在一个姑且文件中。如许当本次下载没有完成的时辰,下次下载的时辰就从这个文件中读取上一次下载的文件长度,然后持续接着上一次的地位开端下载。并且将本次下载的长度写入到这个文件中。

一、下载文件信息类、实体

封装即将下载资料的信息

18800301_1.jpg

18800301_2.jpgDownloadInfo.java

package com.hoo.entity;

/*** function: 下载文件信息类

*@authorhoojo

* @createDate 2011-9-21 下午05:14:58

* @file DownloadInfo.java

* @package com.hoo.entity

* @project MultiThreadDownLoad

* @bloghttp://blog.csdn.net/IBM_hoojo* @email hoojo_@126.com

*@version1.0*/

public class DownloadInfo {

//下载文件url private String url;

//下载文件名称 private String fileName;

//下载文件路径 private String filePath;

//分成几许段下载, 每一段用一个线程完成下载 private int splitter;

//下载文件默认保存路径 private final static String FILE_PATH = "C:/temp";

//默认分块数、线程数 private final static int SPLITTER_NUM = 5;

public DownloadInfo() {

super();

}

/***@paramurl 下载地址*/

public DownloadInfo(String url) {

this(url, null, null, SPLITTER_NUM);

}

/***@paramurl 下载地址url

*@paramsplitter 分成几许段或是几许个线程下载*/

public DownloadInfo(String url, int splitter) {

this(url, null, null, splitter);

}

/***

*@paramurl 下载地址

*@paramfileName 文件名称

*@paramfilePath 文件保存路径

*@paramsplitter 分成几许段或是几许个线程下载*/

public DownloadInfo(String url, String fileName, String filePath, int splitter) {

super();

if (url == null || "".equals(url)) {

throw new RuntimeException("url is not null!");

}

this.url = url;

this.fileName = (fileName == null || "".equals(fileName)) ? getFileName(url) : fileName;

this.filePath = (filePath == null || "".equals(filePath)) ? FILE_PATH : filePath;

this.splitter = (splitter < 1) ? SPLITTER_NUM : splitter;

}

/*** function: 经由过程url获得文件名称

*@authorhoojo

* @createDate 2011-9-30 下午05:00:00

*@paramurl

*@return*/

private String getFileName(String url) {

return url.substring(url.lastIndexOf("/") + 1, url.length());

}

public String getUrl() {

return url;

}

public void setUrl(String url) {

if (url == null || "".equals(url)) {

throw new RuntimeException("url is not null!");

}

this.url = url;

}

public String getFileName() {

return fileName;

}

public void setFileName(String fileName) {

this.fileName = (fileName == null || "".equals(fileName)) ? getFileName(url) : fileName;

}

public String getFilePath() {

return filePath;

}

public void setFilePath(String filePath) {

this.filePath = (filePath == null || "".equals(filePath)) ? FILE_PATH : filePath;

}

public int getSplitter() {

return splitter;

}

public void setSplitter(int splitter) {

this.splitter = (splitter < 1) ? SPLITTER_NUM : splitter;

}

@Override

public String toString() {

return this.url + "#" + this.fileName + "#" + this.filePath + "#" + this.splitter;

}

}

二、随机写入一段文件

这个类主如果完成向本地的指定文件指针出开端写入文件,并返回当前写入文件的长度(文件指针)。这个类将被线程调用,文件被分成对应的块后,将被线程调用。每个线程都将会调用这个类完成文件的随机写入。

18800301_3.jpg

18800301_4.jpgSaveItemFile.java

package com.hoo.download;

import java.io.IOException;

import java.io.RandomAccessFile;

/*** function: 写入文件、保存文件

*@authorhoojo

* @createDate 2011-9-21 下午05:44:02

* @file SaveItemFile.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @bloghttp://blog.csdn.net/IBM_hoojo* @email hoojo_@126.com

*@version1.0*/

public class SaveItemFile {

//存储文件 private RandomAccessFile itemFile;

public SaveItemFile() throws IOException {

this("", 0);

}

/***@paramname 文件路径、名称

*@parampos 写入点地位 position

*@throwsIOException*/

public SaveItemFile(String name, long pos) throws IOException {

itemFile = new RandomAccessFile(name, "rw");

//在指定的pos地位开端写入数据 itemFile.seek(pos);

}

/*** function: 同步办法写入文件

*@authorhoojo

* @createDate 2011-9-26 下午12:21:22

*@parambuff 缓冲数组

*@paramstart 肇端地位

*@paramlength 长度

*@return*/

public synchronized int write(byte[] buff, int start, int length) {

int i = -1;

try {

itemFile.write(buff, start, length);

i = length;

} catch (IOException e) {

e.printStackTrace();

}

return i;

}

public void close() throws IOException {

if (itemFile != null) {

itemFile.close();

}

}

}

三、单个线程下载文件

这个类主如果完成单个线程的文件下载,将经由过程URLConnection读取指定url的资料信息。然后用InputStream读取文件内容,然后调用调用SaveItemFile类,向本地写入当前要读取的块的内容。

18800301_5.jpg

18800301_6.jpgDownloadFile.java

package com.hoo.download;

import java.io.IOException;

import java.io.InputStream;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;

import java.net.URLConnection;

import com.hoo.util.LogUtils;

/*** function: 单线程下载文件

*@authorhoojo

* @createDate 2011-9-22 下午02:55:10

* @file DownloadFile.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @bloghttp://blog.csdn.net/IBM_hoojo* @email hoojo_@126.com

*@version1.0*/

public class DownloadFile extends Thread {

//下载文件url private String url;

//下载文件肇端地位 private long startPos;

//下载文件停止地位 private long endPos;

//线程id private int threadId;

//下载是否完成 private boolean isDownloadOver = false;

private SaveItemFile itemFile;

private static final int BUFF_LENGTH = 1024 * 8;

/***@paramurl 下载文件url

*@paramname 文件名称

*@paramstartPos 下载文件出发点

*@paramendPos 下载文件停止点

*@paramthreadId 线程id

*@throwsIOException*/

public DownloadFile(String url, String name, long startPos, long endPos, int threadId) throws IOException {

super();

this.url = url;

this.startPos = startPos;

this.endPos = endPos;

this.threadId = threadId;

//分块下载写入文件内容 this.itemFile = new SaveItemFile(name, startPos);

}

@Override

public void run() {

while (endPos > startPos && !isDownloadOver) {

try {

URL url = new URL(this.url);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

//设置连接超不时候为10000ms conn.setConnectTimeout(10000);

//设置读取数据超不时候为10000ms conn.setReadTimeout(10000);

setHeader(conn);

String property = "bytes=" + startPos + "-";

conn.setRequestProperty("RANGE", property);

//输出log信息 LogUtils.log("开端 " + threadId + ":" + property + endPos);

//printHeader(conn);//获取文件输入流,读取文件内容 InputStream is = conn.getInputStream();

byte[] buff = new byte[BUFF_LENGTH];

int length = -1;

LogUtils.log("#start#Thread: " + threadId + ", startPos: " + startPos + ", endPos: " + endPos);

while ((length = is.read(buff)) > 0 && startPos < endPos && !isDownloadOver) {

//写入文件内容,返回最后写入的长度 startPos += itemFile.write(buff, 0, length);

}

LogUtils.log("#over#Thread: " + threadId + ", startPos: " + startPos + ", endPos: " + endPos);

LogUtils.log("Thread " + threadId + " is execute over!");

this.isDownloadOver = true;

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

if (itemFile != null) {

itemFile.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

if (endPos < startPos && !isDownloadOver) {

LogUtils.log("Thread " + threadId + " startPos > endPos, not need download file !");

this.isDownloadOver = true;

}

if (endPos == startPos && !isDownloadOver) {

LogUtils.log("Thread " + threadId + " startPos = endPos, not need download file !");

this.isDownloadOver = true;

}

}

/*** function: 打印下载文件头部信息

*@authorhoojo

* @createDate 2011-9-22 下午05:44:35

*@paramconn HttpURLConnection*/

public static void printHeader(URLConnection conn) {

int i = 1;

while (true) {

String header = conn.getHeaderFieldKey(i);

i++;

if (header != null) {

LogUtils.info(header + ":" + conn.getHeaderField(i));

} else {

break;

}

}

}

/*** function: 设置URLConnection的头部信息,假装恳求信息

*@authorhoojo

* @createDate 2011-9-28 下午05:29:43

*@paramcon*/

public static void setHeader(URLConnection conn) {

conn.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");

conn.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");

conn.setRequestProperty("Accept-Encoding", "utf-8");

conn.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");

conn.setRequestProperty("Keep-Alive", "300");

conn.setRequestProperty("connnection", "keep-alive");

conn.setRequestProperty("If-Modified-Since", "Fri, 02 Jan 2009 17:00:05 GMT");

conn.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");

conn.setRequestProperty("Cache-conntrol", "max-age=0");

conn.setRequestProperty("Referer", "http://www.baidu.com");

}

public boolean isDownloadOver() {

return isDownloadOver;

}

public long getStartPos() {

return startPos;

}

public long getEndPos() {

return endPos;

}

}

四、分段多线程写入文件内容

这个类主如果完成读取指定url资料的内容,获取该资料的长度。然后将该资料分成指定的块数,将每块的肇端下载地位、停止下载地位,分别保存在一个数组中。每块都零丁开辟一个自力线程开端下载。在开端下载之前,须要创建一个姑且文件,写入当前下载线程的开端下载指针地位和停止下载指针地位。

18800301_7.jpg

18800301_8.jpgBatchDownloadFile.java

package com.hoo.download;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;

import com.hoo.entity.DownloadInfo;

import com.hoo.util.LogUtils;

/*** function: 分批量下载文件

*@authorhoojo

* @createDate 2011-9-22 下午05:51:54

* @file BatchDownloadFile.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @bloghttp://blog.csdn.net/IBM_hoojo* @email hoojo_@126.com

*@version1.0*/

public class BatchDownloadFile implements Runnable {

//下载文件信息 private DownloadInfo downloadInfo;

//一组开端下载地位 private long[] startPos;

//一组停止下载地位 private long[] endPos;

//休眠时候 private static final int SLEEP_SECONDS = 500;

//子线程下载 private DownloadFile[] fileItem;

//文件长度 private int length;

//是否第一个文件 private boolean first = true;

//是否停止下载 private boolean stop = false;

//姑且文件信息 private File tempFile;

public BatchDownloadFile(DownloadInfo downloadInfo) {

this.downloadInfo = downloadInfo;

String tempPath = this.downloadInfo.getFilePath() + File.separator + downloadInfo.getFileName() + ".position";

tempFile = new File(tempPath);

//若是存在读入点地位的文件 if (tempFile.exists()) {

first = false;

//就直接读取内容 try {

readPosInfo();

} catch (IOException e) {

e.printStackTrace();

}

} else {

//数组的长度就要分成几许段的数量 startPos = new long[downloadInfo.getSplitter()];

endPos = new long[downloadInfo.getSplitter()];

}

}

@Override

public void run() {

//初次下载,获取下载文件长度 if (first) {

length = this.getFileSize();//获取文件长度 if (length == -1) {

LogUtils.log("file length is know!");

stop = true;

} else if (length == -2) {

LogUtils.log("read file length is error!");

stop = true;

} else if (length > 0) {

/*** eg

* start: 1, 3, 5, 7, 9

* end: 3, 5, 7, 9, length*/

for (int i = 0, len = startPos.length; i < len; i++) {

int size = i * (length / len);

startPos[i] = size;

//设置最后一个停止点的地位 if (i == len - 1) {

endPos[i] = length;

} else {

size = (i + 1) * (length / len);

endPos[i] = size;

}

LogUtils.log("start-end Position[" + i + "]: " + startPos[i] + "-" + endPos[i]);

}

} else {

LogUtils.log("get file length is error, download is stop!");

stop = true;

}

}

//子线程开端下载 if (!stop) {

//创建单线程下载对象数组 fileItem = new DownloadFile[startPos.length];//startPos.length = downloadInfo.getSplitter() for (int i = 0; i < startPos.length; i++) {

try {

//创建指定个数单线程下载对象,每个线程自力完成指定块内容的下载 fileItem[i] = new DownloadFile(

downloadInfo.getUrl(),

this.downloadInfo.getFilePath() + File.separator + downloadInfo.getFileName(),

startPos[i], endPos[i], i

);

fileItem[i].start();//启动线程,开端下载 LogUtils.log("Thread: " + i + ", startPos: " + startPos[i] + ", endPos: " + endPos[i]);

} catch (IOException e) {

e.printStackTrace();

}

}

//轮回写入下载文件长度信息 while (!stop) {

try {

writePosInfo();

LogUtils.log("downloading……");

Thread.sleep(SLEEP_SECONDS);

stop = true;

} catch (IOException e) {

e.printStackTrace();

} catch (InterruptedException e) {

e.printStackTrace();

}

for (int i = 0; i < startPos.length; i++) {

if (!fileItem[i].isDownloadOver()) {

stop = false;

break;

}

}

}

LogUtils.info("Download task is finished!");

}

}

/*** 将写入点数据保存在姑且文件中

*@authorhoojo

* @createDate 2011-9-23 下午05:25:37

*@throwsIOException*/

private void writePosInfo() throws IOException {

DataOutputStream dos = new DataOutputStream(new FileOutputStream(tempFile));

dos.writeInt(startPos.length);

for (int i = 0; i < startPos.length; i++) {

dos.writeLong(fileItem[i].getStartPos());

dos.writeLong(fileItem[i].getEndPos());

//LogUtils.info("[" + fileItem[i].getStartPos() + "#" + fileItem[i].getEndPos() + "]"); }

dos.close();

}

/*** function:读取写入点的地位信息

*@authorhoojo

* @createDate 2011-9-23 下午05:30:29

*@throwsIOException*/

private void readPosInfo() throws IOException {

DataInputStream dis = new DataInputStream(new FileInputStream(tempFile));

int startPosLength = dis.readInt();

startPos = new long[startPosLength];

endPos = new long[startPosLength];

for (int i = 0; i < startPosLength; i++) {

startPos[i] = dis.readLong();

endPos[i] = dis.readLong();

}

dis.close();

}

/*** function: 获取下载文件的长度

*@authorhoojo

* @createDate 2011-9-26 下午12:15:08

*@return*/

private int getFileSize() {

int fileLength = -1;

try {

URL url = new URL(this.downloadInfo.getUrl());

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

DownloadFile.setHeader(conn);

int stateCode = conn.getResponseCode();

//断定http status是否为HTTP/1.1 206 Partial Content或者200 OK if (stateCode != HttpURLConnection.HTTP_OK && stateCode != HttpURLConnection.HTTP_PARTIAL) {

LogUtils.log("Error Code: " + stateCode);

return -2;

} else if (stateCode >= 400) {

LogUtils.log("Error Code: " + stateCode);

return -2;

} else {

//获取长度 fileLength = conn.getContentLength();

LogUtils.log("FileLength: " + fileLength);

}

//读取文件长度 /*for (int i = 1; ; i++) {

String header = conn.getHeaderFieldKey(i);

if (header != null) {

if ("Content-Length".equals(header)) {

fileLength = Integer.parseInt(conn.getHeaderField(i));

break;

}

} else {

break;

}

}*/

DownloadFile.printHeader(conn);

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

return fileLength;

}

}

五、对象类、测试类

日记对象类

18800301_9.jpg

18800301_10.jpgLogUtils.java

package com.hoo.util;

/*** function: 日记对象类

*@authorhoojo

* @createDate 2011-9-21 下午05:21:27

* @file LogUtils.java

* @package com.hoo.util

* @project MultiThreadDownLoad

* @bloghttp://blog.csdn.net/IBM_hoojo* @email hoojo_@126.com

*@version1.0*/

public abstract class LogUtils {

public static void log(Object message) {

System.err.println(message);

}

public static void log(String message) {

System.err.println(message);

}

public static void log(int message) {

System.err.println(message);

}

public static void info(Object message) {

System.out.println(message);

}

public static void info(String message) {

System.out.println(message);

}

public static void info(int message) {

System.out.println(message);

}

}

下载对象类

18800301_11.jpg

18800301_12.jpgDownloadUtils.java

package com.hoo.util;

import com.hoo.download.BatchDownloadFile;

import com.hoo.entity.DownloadInfo;

/*** function: 分块多线程下载对象类

*@authorhoojo

* @createDate 2011-9-28 下午05:22:18

* @file DownloadUtils.java

* @package com.hoo.util

* @project MultiThreadDownLoad

* @bloghttp://blog.csdn.net/IBM_hoojo* @email hoojo_@126.com

*@version1.0*/

public abstract class DownloadUtils {

public static void download(String url) {

DownloadInfo bean = new DownloadInfo(url);

LogUtils.info(bean);

BatchDownloadFile down = new BatchDownloadFile(bean);

new Thread(down).start();

}

public static void download(String url, int threadNum) {

DownloadInfo bean = new DownloadInfo(url, threadNum);

LogUtils.info(bean);

BatchDownloadFile down = new BatchDownloadFile(bean);

new Thread(down).start();

}

public static void download(String url, String fileName, String filePath, int threadNum) {

DownloadInfo bean = new DownloadInfo(url, fileName, filePath, threadNum);

LogUtils.info(bean);

BatchDownloadFile down = new BatchDownloadFile(bean);

new Thread(down).start();

}

}

下载测试类

18800301_13.jpg

18800301_14.jpgTestDownloadMain.java

package com.hoo.test;

import com.hoo.util.DownloadUtils;

/*** function: 下载测试

*@authorhoojo

* @createDate 2011-9-23 下午05:49:46

* @file TestDownloadMain.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @bloghttp://blog.csdn.net/IBM_hoojo* @email hoojo_@126.com

*@version1.0*/

public class TestDownloadMain {

public static void main(String[] args) {

/*DownloadInfo bean = new DownloadInfo("http://i7.meishichina.com/Health/UploadFiles/201109/2011092116224363.jpg");

System.out.println(bean);

BatchDownloadFile down = new BatchDownloadFile(bean);

new Thread(down).start();*/

//DownloadUtils.download("http://i7.meishichina.com/Health/UploadFiles/201109/2011092116224363.jpg"); DownloadUtils.download("http://mp3.baidu.com/j?j=2&url=http%3A%2F%2Fzhangmenshiting2.baidu.com%2Fdata%2Fmusic%2F1669425%2F%25E9%2599%25B7%25E5%2585%25A5%25E7%2588%25B1%25E9%2587%258C%25E9%259D%25A2.mp3%3Fxcode%3D2ff36fb70737c816553396c56deab3f1", "aa.mp3", "c:/temp", 5);

}

}

多线程下载首要在第三部和第四部,其他的处所还是很好懂得。源码中供给响应的注释了,便于懂得。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值