亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Java > 正文

使用java實現http多線程斷點下載文件(二)

2019-11-26 16:16:29
字體:
來源:轉載
供稿:網友

下載工具我想沒有幾個人不會用的吧,前段時間比較無聊,花了點時間用java寫了個簡單的http多線程下載程序,純粹是無聊才寫的,只實現了幾個簡單的功能,而且也沒寫界面,今天正好也是一個無聊日,就拿來寫篇文章,班門弄斧一下,覺得好給個掌聲,不好也不要噴,謝謝!
我實現的這個http下載工具功能很簡單,就是一個多線程以及一個斷點恢復,當然下載是必不可少的。那么大概先整理一下要做的事情
1、連接資源服務器,獲取資源信息,創建文件
2、切分資源,多線程下載
3、斷點恢復功能
4、下載速率統計
大概就這幾點吧,那么首先要做的就是連接資源并獲取資源信息,我這里使用了JavaSE自帶的URLConnection進行資源連接,大致代碼如下:

復制代碼 代碼如下:

String urlStr = “http://www.sourcelink.com/download/xxx”; //資源地址,隨便寫的
URL url = new URL(urlStr); //創建URL
URLConnection con = url.openConnection(); //建立連接
contentLen = con.getContentLength(); //獲得資源長度
File file = new File(filename); //根據filename創建一個下載文件,也會是我們最終下載所得的文件

很簡單吧,沒錯就是這么簡單,第一步做完了,那么接下來要做第二步,切分資源,實現多線程。在上一步我們已經獲得了資源的長度contentLen,那么如何根據這個對資源進行切分呢?假如我們要運行十個線程,那么我們就先把contentLen處以10,獲得每塊的大小,然后在分別創建十個線程,每個線程負責其中一塊的寫入,這就需要利用到RandomAccessFile這個類了,這個類提供了對文件的隨機訪問,可以指定向文件中的某一個位置進行寫入操作,大致代碼如下:
復制代碼 代碼如下:

long subLen = contentLen / threadQut; //獲取每塊的大小
//創建十個線程,并啟動線程
for (int i = 0; i < threadQut; i++) {
DLThread thread = new DLThread(this, i + 1, subLen * i, subLen * (i + 1) - 1); //創建線程
dlThreads[i] = thread;
QSEngine.pool.execute(dlThreads[i]); //把線程交給線程池進行管理
}

在這里使用到了DLThread這個類,我們先來看看這個類的構造方法的定義:
public DLThread(DLTask dlTask, int id, long startPos, long endPos)
第一個參數為一個DLTask,這個類就代表一個下載任務,里面主要保存這一個下載任務的信息,包括下載資源名,本地文件名等等的信息。第二個參數就是一個標示線程的id,如果有10個線程,那么這個id就是從1到10,第三個參數startPos代表該線程從文件的哪個地方開始寫入,最后一個參數endPos代表寫到哪里就結束。
我們再來看看,一個線程啟動后,具體如何去下載,請看run方法:
復制代碼 代碼如下:

public void run() {
System.out.println("線程" + id + "啟動");
BufferedInputStream bis = null; //創建一個buff
RandomAccessFile fos = null;
byte[] buf = new byte[BUFFER_SIZE]; //緩沖區大小
URLConnection con = null;
try {
con = url.openConnection(); //創建連接,這里會為每個線程都創建一個連接
con.setAllowUserInteraction(true);
if (isNewThread) {
con.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);//設置獲取資源數據的范圍,從startPos到endPos
fos = new RandomAccessFile(file, "rw"); //創建RandomAccessFile
fos.seek(startPos); //從startPos開始
} else {
con.setRequestProperty("Range", "bytes=" + curPos + "-" + endPos);
fos = new RandomAccessFile(dlTask.getFile(), "rw");
fos.seek(curPos);
}
//下面一段向根據文件寫入數據,curPos為當前寫入的未知,這里會判斷是否小于endPos,
//如果超過endPos就代表該線程已經執行完畢
bis = new BufferedInputStream(con.getInputStream());
while (curPos < endPos) {
int len = bis.read(buf, 0, BUFFER_SIZE);
if (len == -1) {
break;
}
fos.write(buf, 0, len);
curPos = curPos + len;
if (curPos > endPos) {
readByte += len - (curPos - endPos) + 1; //獲取正確讀取的字節數
} else {
readByte += len;
}
}
System.out.println("線程" + id + "已經下載完畢。");
this.finished = true;
bis.close();
fos.close();
} catch (IOException ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}

上面的代碼就是根據startPos和endPos對文件機型寫操作,每個線程都有自己獨立的一個資源塊,從startPos到endPos。上面的方式就是線程下載的核心,多線程搞定后,接下來就是實現斷點恢復的功能,其實斷點恢復無非就是記錄下每個線程完成到哪個未知,在這里我就是使用curPos進行的記錄,大家在上面的代碼就應該可以看到,我會記錄下每個線程的curPos,然后在線程重新啟動的時候,就把curPos當成是startPos,而endPost則不變即可,大家有沒注意到run方法里有一段這樣的代碼:
復制代碼 代碼如下:

if (isNewThread) { //判斷是否斷點,如果true,代表是一個新的下載線程,而不是斷點恢復
con.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);//設置獲取資源數據的范圍,從startPos到endPos
fos = new RandomAccessFile(file, "rw"); //創建RandomAccessFile
fos.seek(startPos); //從startPos開始
} else {
con.setRequestProperty("Range", "bytes=" + curPos + "-" + endPos);//使用curPos替代startPos,其他都和新創建一個是一樣的。
fos = new RandomAccessFile(dlTask.getFile(), "rw");
fos.seek(curPos);
}

上面就是斷點恢復的做法了,和新創建一個線程沒什么不同,只是startPos不一樣罷了,其他都一樣,不過僅僅有這個還不夠,因為如果程序關閉的話,這些信息又是如何保存呢?例如文件名啊,每個線程的curPos啊等等,大家在使用下載軟件的時候,相信都會發現在軟件沒下載完的時候,在目錄下會有兩個臨時文件,而其中一個就是用來保存下載任務的信息的,如果沒有這些信息,程序是不知道該如何恢復下載進度的。而我這里又如何實現的呢?我這個人比較懶,又不想再創建一個文件來保存信息,然后自己又要讀取信息創建對象,那太麻煩了,所以我想到了java提供序列化機制,我的想法就是直接把整個DLTask的對象序列化到硬盤上,上面說過DLTask這個類就是用來保存每個任務的信息的,所以我只要在需要恢復的時候,反序列化這個對象,就可以很容易的實現了斷點功能,我們來看看這個對象保存的信息:
復制代碼 代碼如下:

public class DLTask extends Thread implements Serializable {
private static final long serialVersionUID = 126148287461276024L;
private final static int MAX_DLTHREAD_QUT = 10; //最大下載線程數量
/** *//**
* 下載臨時文件后綴,下載完成后將自動被刪除
*/
public final static String FILE_POSTFIX = ".tmp";
private URL url;
private File file;
private String filename;
private int id;
private int Level;
private int threadQut; //下載線程數量,用戶可定制
private int contentLen; //下載文件長度
private long completedTot; //當前下載完成總數
private int costTime; //下載時間計數,記錄下載耗費的時間
private String curPercent; //下載百分比
private boolean isNewTask; //是否新建下載任務,可能是斷點續傳任務
private DLThread[] dlThreads; //保存當前任務的線程
transient private DLListener listener; //當前任務的監聽器,用于即時獲取相關下載信息

如上代碼,這個對象實現了Serializable接口,保存了任務的所有信息,還包括有每個線程對象dlThreads,這樣子就可以很容易做到斷點的恢復了,讓我重新寫一個文件保存這些信息,然后在恢復的時候再根據這些信息創建一個對象,那簡直是要我的命。這里創建了一個方法,用于斷點恢復用:
復制代碼 代碼如下:

private void resumeTask() {
listener = new DLListener(this);
file = new File(filename);
for (int i = 0; i < threadQut; i++) {
dlThreads[i].setDlTask(this);
QSEngine.pool.execute(dlThreads[i]);
}
QSEngine.pool.execute(listener);
}

實際上就是減少了先連接資源,然后進行切分資源的代碼,因為這些信息已經都被保存在DLTask的對象下了。
看到上面的代碼,不知道大家注意到有一個對象DLListener沒有,這個對象實際上就是用于監聽整個任務的信息的,這里我主要用于兩個目的,一個是定時的對DLTask進行序列化,保存任務信息,用于斷點恢復,一個就是進行下載速率的統計,平均多長時間進行一個統計。我們先來看下它的代碼,這個類也是一個單獨的線程:
復制代碼 代碼如下:

public void run() {
int i = 0;
BigDecimal completeTot = null; //完成的百分比
long start = System.currentTimeMillis(); //當前時間,用于記錄開始統計時間
long end = start;
while (!dlTask.isComplete()) { //整個任務是否完成,沒有完成則繼續循環
i++;
String percent = dlTask.getCurPercent(); //獲取當前的完成百分數
completeTot = new BigDecimal(dlTask.getCompletedTot()); //獲取當前完成的總字節數
//獲得當前時間,然后與start時間比較,如果不一樣,利用當前完成的總數除以所使用的時間,獲得一個平均下載速度
end = System.currentTimeMillis();
if (end - start != 0) {
BigDecimal pos = new BigDecimal(((end - start) / 1000) * 1024);
System.out.println("Speed :"
+ completeTot
.divide(pos, 0, BigDecimal.ROUND_HALF_EVEN)
+ "k/s " + percent + "% completed. ");
}
recoder.record(); //將任務信息記錄到硬盤
try {
sleep(3000);
} catch (InterruptedException ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
//以下是下載完成后打印整個下載任務的信息
int costTime =+ (int)((System.currentTimeMillis() - start) / 1000);
dlTask.setCostTime(costTime);
String time = QSDownUtils.changeSecToHMS(costTime);
dlTask.getFile().renameTo(new File(dlTask.getFilename()));
System.out.println("Download finished. " + time);
}

這個方法中的recoder.record()方法的調用就是用于序列化任務對象,其他的代碼均為統計信息用的,具體可看注釋,record該方法的代碼如下:
復制代碼 代碼如下:

public void record() {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(dlTask.getFilename() + ".tsk"));
out.writeObject(dlTask);
out.close();
} catch (IOException ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
} finally {
try {
out.close();
} catch (IOException ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}

到這里,大致的代碼都完成了,不過以上的代碼都是部分片段,只是作為一個參考給大家看下,而且由于本人水平有限,代碼很多地方都沒有經過過多的考慮,沒有經過優化,僅僅只是自娛自樂,所以可能有很多地方都寫的很爛,這個程序也缺乏很多功能,連界面都沒有,所以整個程序的代碼就不上傳了,免得丟人,呵呵。希望對有興趣的朋友盡到一點幫助吧。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
2023亚洲男人天堂| 国产精品入口免费视| 欧美成人精品影院| 精品日本美女福利在线观看| 亚洲香蕉成视频在线观看| 久久久久中文字幕| 成人欧美一区二区三区在线| 亚洲已满18点击进入在线看片| 欧美在线影院在线视频| 国产精品成人播放| 国产一区二区三区在线播放免费观看| 久久久久国产一区二区三区| 亚洲第一精品久久忘忧草社区| 欧美国产一区二区三区| 国产成人精品视频| 国产日本欧美一区二区三区| 欧美黄色片在线观看| 精品久久久一区二区| 91国产视频在线播放| 久久99久久99精品中文字幕| 91在线观看欧美日韩| 久久人体大胆视频| 日本欧美精品在线| 欧美亚洲第一区| 日韩成人在线观看| 亚洲国产精品久久| 亚洲无亚洲人成网站77777| 8x海外华人永久免费日韩内陆视频| 久久久免费精品视频| 亚洲欧美国产日韩天堂区| 中文字幕av一区二区三区谷原希美| 久久香蕉国产线看观看av| 日韩美女av在线| 成人激情视频在线观看| 日韩电视剧免费观看网站| 久久人人爽人人爽人人片av高清| 国产小视频国产精品| 亚洲国产小视频在线观看| 国产精品爱久久久久久久| 91视频-88av| 国产精品成人一区二区三区吃奶| 国产精品亚洲аv天堂网| 欧美成人激情图片网| 狠狠躁夜夜躁久久躁别揉| 国产一区二区黄| 国产精品亚洲综合天堂夜夜| 精品一区电影国产| 欧美老女人性视频| 综合网日日天干夜夜久久| 国产精品入口福利| 日韩小视频在线| 在线播放精品一区二区三区| 国产精品视频免费在线观看| 日本国产精品视频| 国产精品主播视频| 在线电影欧美日韩一区二区私密| 日韩中文字幕在线视频| 亚洲精品电影网站| 国产精品情侣自拍| 久久久久一本一区二区青青蜜月| 成人福利视频在线观看| 久久精品成人欧美大片古装| 韩国一区二区电影| 欧美猛少妇色xxxxx| 精品国产一区二区在线| 亚洲国产精品国自产拍av秋霞| 欧美日韩免费观看中文| 色先锋资源久久综合5566| 国模精品系列视频| 国产精品视频久久久| 97在线观看视频国产| 中文在线不卡视频| 国产美女高潮久久白浆| 日韩av免费观影| 欧洲亚洲免费在线| 日韩中文字幕免费| 欧美日韩不卡合集视频| 国产精品免费久久久久影院| 日韩中文字幕免费视频| 中文字幕不卡在线视频极品| 国产精品国产三级国产aⅴ浪潮| 在线观看日韩专区| 国产美女精品视频免费观看| 欧美大成色www永久网站婷| 国产偷国产偷亚洲清高网站| 91免费国产网站| 亚洲18私人小影院| 国产日韩欧美中文在线播放| 亚洲欧美一区二区三区久久| 国产成人一区三区| 欧美激情视频一区二区| 91在线高清免费观看| 久久99久国产精品黄毛片入口| 久久综合网hezyo| 久久夜色精品亚洲噜噜国产mv| 久久成人亚洲精品| 国产精品电影在线观看| 一本色道久久综合狠狠躁篇怎么玩| 久久综合久久美利坚合众国| 欧美怡春院一区二区三区| 国产精品成av人在线视午夜片| 欧美高清视频免费观看| 少妇精69xxtheporn| 成人久久一区二区| 欧美国产日韩一区二区三区| 欧美高清第一页| 欧美激情影音先锋| 日韩电影在线观看免费| 国产精品久久久久久久久免费| 亚洲欧美日韩另类| 亚洲国产小视频在线观看| 欧美洲成人男女午夜视频| 奇门遁甲1982国语版免费观看高清| 欧美性xxxx极品高清hd直播| 日韩精品视频观看| 欧美日韩国产成人高清视频| 欧美在线视频a| 欧美片一区二区三区| 中文字幕日韩在线播放| 欧日韩在线观看| 亚洲香蕉伊综合在人在线视看| 国产午夜一区二区| 久久久91精品国产一区不卡| 久久久精品2019中文字幕神马| 国产精品一区二区三区久久久| 亚洲精品一区二区网址| 亚洲最大激情中文字幕| 亚洲视频在线观看| 日韩精品中文字幕视频在线| 欧美色道久久88综合亚洲精品| 国产91精品青草社区| 欧美激情女人20p| 97国产一区二区精品久久呦| 久久精品人人爽| 国产美女搞久久| 俺去亚洲欧洲欧美日韩| 浅井舞香一区二区| 亚洲日本中文字幕免费在线不卡| 55夜色66夜色国产精品视频| 久久99热这里只有精品国产| 国产激情综合五月久久| 91精品美女在线| 日韩成人在线视频| 91高清免费视频| 伊人久久综合97精品| 欧美性猛交xxxx乱大交蜜桃| 久久精品小视频| 538国产精品一区二区在线| 日本成人精品在线| 亚洲美女性视频| 亚洲天堂视频在线观看| 久久国产精品久久国产精品| 亚洲黄页视频免费观看| 日韩欧美主播在线| 中文字幕日韩在线观看| 欧美在线观看一区二区三区| 色先锋资源久久综合5566| 欧美在线免费视频| 日韩毛片中文字幕| 国产精品久久久av| 国产一区二区三区视频在线观看| 国产精品久久久久久影视| 欧美黑人xxxⅹ高潮交| 国产精品久久久久99|