java多線程實現(xiàn)文件下載
更新時間:2021年08月26日 10:21:56 作者:ljlleo2013
這篇文章主要為大家詳細(xì)介紹了java多線程實現(xiàn)文件下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
本文實例為大家分享了java多線程實現(xiàn)文件下載的具體代碼,供大家參考,具體內(nèi)容如下
1、DownloadManager類
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class DownloadManager implements Runnable {
// 保存路徑
private String savePath;
// 總的下載線程數(shù)
private int threadNum;
// 下載的鏈接地址
private String urlFile;
// 是否下載開始
private boolean isStarted;
// 用于監(jiān)視何時合并文件存放Thread的list
private List<DownloadThread> downloadList = new ArrayList<DownloadThread>();
public DownloadManager(String savePath, int threadNum, String urlFile) {
super();
this.savePath = savePath;
this.threadNum = threadNum;
this.urlFile = urlFile;
}
// 最終調(diào)用線程下載。本線程中調(diào)用分線程。
public void action() {
new Thread(this).start();
}
public void run() {
long t1 = System.currentTimeMillis();
System.out.println(t1);
// 如果沒有下載 , 就開始 , 并且將已經(jīng)下載的變量值設(shè)為true
if (!isStarted) {
startDownload();
isStarted = true;
}
while (true) {
// 初始化認(rèn)為所有線程下載完成,逐個檢查
boolean finish = true;
// 如果有任何一個沒完成,說明下載沒完成,不能合并文件
for (DownloadThread thread : downloadList) {
if (!thread.isFinish()) {
finish = false;
break;
}
}
// 全部下載完成才為真
if (finish) {
// 合并文件
mergeFiles();
// 跳出循環(huán) , 下載結(jié)束
break;
}
// 休息一會 , 減少cpu消耗
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long t2 = System.currentTimeMillis();
System.out.println(t2);
System.out.println("下載用時:" + (t2 -t1));
}
public void startDownload() {
// 得到每個線程開始值 , 下載字節(jié)數(shù)大小
int[][] posAndLength = getPosAndLength();
// 根據(jù)下載信息創(chuàng)建每個下載線程,并且啟動他們。
for (int i = 0; i < posAndLength.length; i++) {
int pos = posAndLength[i][0];
int length = posAndLength[i][1];
DownloadThread downloadThread = new DownloadThread(i + 1, length,
pos, savePath, urlFile);
new Thread(downloadThread).start();
downloadList.add(downloadThread);
}
}
/**
* 獲得文件大小
*
* @return 文件大小
*/
public long getFileLength() {
System.out.println("獲得文件大小 start......");
HttpURLConnection conn = null;
long result = 0;
try {
URL url = new URL(urlFile);
conn = (HttpURLConnection) url.openConnection();
// 使用Content-Length頭信息獲得文件大小
result = Long.parseLong(conn.getHeaderField("Content-Length"));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
System.out.println("獲得文件大小 end......" + result);
return result;
}
// 具體細(xì)節(jié)求出每個線程的開始位置和文件下載大小
public int[][] getPosAndLength() {
int[][] result = new int[threadNum][2];
int fileLength = (int) getFileLength();
int every = fileLength % threadNum == 0 ? fileLength / threadNum
: fileLength / threadNum + 1;
for (int i = 0; i < result.length; i++) {
int length = 0;
if (i != result.length - 1) {
length = every;
} else {
length = fileLength - i * every;
}
result[i][0] = i * every;
result[i][1] = length;
}
return result;
}
// 合并文件
public void mergeFiles() {
System.out.println("合并文件 start......");
OutputStream out = null;
try {
out = new FileOutputStream(savePath);
for (int i = 1; i <= threadNum; i++) {
InputStream in = new FileInputStream(savePath + i);
byte[] bytes = new byte[2048];
int read = 0;
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
out.flush();
}
if (in != null) {
in.close();
new File(savePath + i).delete();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("合并文件 end......");
}
public String getSavePath() {
return savePath;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public int getThreadNum() {
return threadNum;
}
public void setThreadNum(int threadNum) {
this.threadNum = threadNum;
}
public String getUrlFile() {
return urlFile;
}
public void setUrlFile(String urlFile) {
this.urlFile = urlFile;
}
public boolean isStarted() {
return isStarted;
}
public void setStarted(boolean isStarted) {
this.isStarted = isStarted;
}
public List<DownloadThread> getDownloadList() {
return downloadList;
}
public void setDownloadList(List<DownloadThread> downloadList) {
this.downloadList = downloadList;
}
}
2、DownloadThread類
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class DownloadThread implements Runnable {
// 當(dāng)前第幾個線程 , 用于給下載文件起名 file1 file2 file3 ...
private int whichThread;
// 監(jiān)聽單一線程下載是否完成
private boolean isFinish;
// 本線程要下載的文件字節(jié)數(shù)
private int length;
// 本線程向服務(wù)器發(fā)送請求時輸入流的首位置
private int startPosition;
// 保存的路徑
private String savePath;
// 要下載的文件 , 用于創(chuàng)建連接
private String url;
public void run() {
HttpURLConnection conn = null;
InputStream in = null;
OutputStream out = null;
try {
System.out.println("正在執(zhí)行的線程:" + whichThread);
URL fileUrl = new URL(url);
// 與服務(wù)器創(chuàng)建連接
conn = (HttpURLConnection) fileUrl.openConnection();
// 下載使用get請求
conn.setRequestMethod("GET");
// 告訴服務(wù)器 , 我是火狐 , 不要不讓我下載。
conn.setRequestProperty(
"User-Agent",
"Firefox Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3");
// 這里是設(shè)置文件輸入流的首位置
conn.setRequestProperty("Range", "bytes=" + startPosition + "-");
// 與服務(wù)器創(chuàng)建連接
conn.connect();
// 獲得輸入流
in = conn.getInputStream();
// 在硬盤上創(chuàng)建file1 , file2 , ...這樣的文件 , 準(zhǔn)備往里面寫東西
out = new FileOutputStream(savePath + whichThread);
// 用于寫入的字節(jié)數(shù)組
byte[] bytes = new byte[4096];
// 一共下載了多少字節(jié)
int count = 0;
// 單次讀取的字節(jié)數(shù)
int read = 0;
while ((read = in.read(bytes)) != -1) {
// 檢查一下是不是下載到了本線程需要的長度
if (length - count < bytes.length) {
// 比如說本線程還需要900字節(jié),但是已經(jīng)讀取1000
// 字節(jié),則用要本線程總下載長度減去
// 已經(jīng)下載的長度
read = length - count;
}
// 將準(zhǔn)確的字節(jié)寫入輸出流
out.write(bytes, 0, read);
// 已經(jīng)下載的字節(jié)數(shù)加上本次循環(huán)字節(jié)數(shù)
count = count + read;
// 如果下載字節(jié)達(dá)到本線程所需要字節(jié)數(shù),消除循環(huán),
// 停止下載
if (count == length) {
break;
}
}
// 將監(jiān)視變量設(shè)置為true
isFinish = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 最后進(jìn)行輸入、輸出、連接的關(guān)閉
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (conn != null) {
conn.disconnect();
}
}
}
public int getStartPosition() {
return startPosition;
}
public void setStartPosition(int startPosition) {
this.startPosition = startPosition;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getWhichThread() {
return whichThread;
}
public void setWhichThread(int whichThread) {
this.whichThread = whichThread;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public String getSavePath() {
return savePath;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public DownloadThread(int whichThread, int length, int startPosition,
String savePath, String url) {
super();
this.whichThread = whichThread;
this.length = length;
this.startPosition = startPosition;
this.savePath = savePath;
this.url = url;
}
public DownloadThread() {
super();
}
public boolean isFinish() {
return isFinish;
}
public void setFinish(boolean isFinish) {
this.isFinish = isFinish;
}
}
3、TestDownload測試類
public class TestDownload {
public static void main(String[] args) {
DownloadManager downloadManager = new DownloadManager("d:/upload/09018417.zip" , 5 , "http://10.1.2.65:8080/cetvossFront/09018417.zip");
downloadManager.action();
}
}
代碼已經(jīng)測試可以運行!
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot項目訪問任意接口出現(xiàn)401錯誤的解決方案
今天小編就為大家分享一篇關(guān)于SpringBoot項目訪問任意接口出現(xiàn)401錯誤的解決方案,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01
springboot啟動時如何指定spring.profiles.active
這篇文章主要介紹了springboot啟動時如何指定spring.profiles.active問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
一篇文章帶你入門java算術(shù)運算符(加減乘除余,字符連接)
這篇文章主要介紹了Java基本數(shù)據(jù)類型和運算符,結(jié)合實例形式詳細(xì)分析了java基本數(shù)據(jù)類型、數(shù)據(jù)類型轉(zhuǎn)換、算術(shù)運算符、邏輯運算符等相關(guān)原理與操作技巧,需要的朋友可以參考下2021-08-08
Java并發(fā)編程中的CyclicBarrier線程屏障詳解
這篇文章主要介紹了Java并發(fā)編程中的CyclicBarrier線程屏障詳解,2023-12-12
深入分析@Resource和@Autowired注解區(qū)別
這篇文章主要為大家介紹了深入分析@Resource和@Autowired注解區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
2023-04-04 
