天天看點

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

基起原根蒂根基理:哄騙URLConnection擷取要下載下傳檔案的長度、頭部等相幹資訊,并設定響應的頭部資訊。并且經由過程URLConnection擷取輸入流,将檔案分成指定的塊,每一塊零丁開辟一個線程完成資料的讀取、寫入。經由過程輸入流讀取下載下傳檔案的資訊,然後将讀取的資訊用RandomAccessFile随機寫入到本地檔案中。同時,每個線程寫入的資料都檔案指針也就是寫入資料的長度,須要儲存在一個姑且檔案中。如許當本次下載下傳沒有完成的時辰,下次下載下傳的時辰就從這個檔案中讀取上一次下載下傳的檔案長度,然後持續接着上一次的地位開端下載下傳。并且将本次下載下傳的長度寫入到這個檔案中。

一、下載下傳檔案資訊類、實體

封裝即将下載下傳資料的資訊

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案
java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

DownloadInfo.java

package com.hoo.entity;

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();

}

public DownloadInfo(String url) {

this(url, null, null, SPLITTER_NUM);

}

public DownloadInfo(String url, int splitter) {

this(url, null, null, splitter);

}

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;

}

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;

}

}

二、随機寫入一段檔案

這個類主如果完成向本地的指定檔案指針出開端寫入檔案,并傳回目前寫入檔案的長度(檔案指針)。這個類将被線程調用,檔案被分成對應的塊後,将被線程調用。每個線程都将會調用這個類完成檔案的随機寫入。

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案
java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

SaveItemFile.java

package com.hoo.download;

import java.io.IOException;

import java.io.RandomAccessFile;

public class SaveItemFile {

//存儲檔案 private RandomAccessFile itemFile;

public SaveItemFile() throws IOException {

this("", 0);

}

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

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

//在指定的pos地位開端寫入資料 itemFile.seek(pos);

}

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類,向本地寫入目前要讀取的塊的内容。

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案
java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

DownloadFile.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;

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;

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;

}

}

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;

}

}

}

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資料的内容,擷取該資料的長度。然後将該資料分成指定的塊數,将每塊的肇端下載下傳地位、停止下載下傳地位,分别儲存在一個數組中。每塊都零丁開辟一個自力線程開端下載下傳。在開端下載下傳之前,須要建立一個姑且檔案,寫入目前下載下傳線程的開端下載下傳指針地位和停止下載下傳指針地位。

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案
java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

BatchDownloadFile.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;

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) {

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!");

}

}

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();

}

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();

}

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);

}

//讀取檔案長度

DownloadFile.printHeader(conn);

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

return fileLength;

}

}

五、對象類、測試類

日記對象類

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案
java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

LogUtils.java

package com.hoo.util;

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);

}

}

下載下傳對象類

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案
java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

DownloadUtils.java

package com.hoo.util;

import com.hoo.download.BatchDownloadFile;

import com.hoo.entity.DownloadInfo;

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();

}

}

下載下傳測試類

java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案
java 多線程下載下傳 斷點_Java 多線程斷點下載下傳檔案

TestDownloadMain.java

package com.hoo.test;

import com.hoo.util.DownloadUtils;

public class TestDownloadMain {

public static void main(String[] args) {

//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);

}

}

多線程下載下傳首要在第三部和第四部,其他的處所還是很好懂得。源碼中供給響應的注釋了,便于懂得。