基於Java多線程的下載器源碼剖析(一)

來源:互聯網
上載者:User

本文實現了一個基於Java多線程的下載器,可提供的功能有:

1. 對檔案使用多線程下載,並顯示每時刻的下載速度。
2. 對多個下載進行管理,包括線程調度,記憶體管理等。

這篇文章的結構如下:首先討論如何?利用Java多線程對單個檔案進行下載。然後討論當系統中有多個檔案下載,如何對這些下載進行管理。包括線程調度,記憶體管理等。

一:單個檔案下載的管理1. 單檔案下載類層次

首先簡要介紹一下單個檔案下載管理的類層次:

來一張圖來表示。



 

  1. 為需要下載的檔案建立一個Download類,Download負責管理該檔案下載時的線程管理、檔案管理、當前速度計算等操作。
  2. 根據線程的數目tNum,將該檔案分為tNum段,每段為一個DownloadBlock。在實際下載的過程中,並不是一次把所有的東西下載完,而是每次下載固定size的一段Di。所以每個DownloadBlock又會分成n段。
  3. 為每個DownloadBlock申請一個線程DownloadThread。其主要作用就是每次下載一段Di,並將其寫入到檔案中。


2. 單檔案下載

對於單個下載,步驟如下

  1. 串連資原始伺服器,擷取資源資訊,建立檔案
  2.  切分資源,為每個線程分配固定的下載地區。


1)封裝下載的屬性

在建立下載之前,我們把每一次下載進行抽象封裝。

首先把URL、目標檔案等封裝在一個DownloadConfig類中。

其中包含了4個屬性:

private URL url; //檔案private File file; //下載檔案儲存目標檔案private int nthread; //下載該檔案需要的線程數private int priority; //該下載的優先順序


如下如所示:



2)串連資原始伺服器,擷取資源資訊,建立檔案,並指定檔案大小

length = config.getUrl().openConnection().getContentLength();RandomAccessFile file = new RandomAccessFile(config.getFile(), "rw");file.setLength(length);file.close();


3)切分資源,為每個線程分配固定的下載地區,並將當前的下載加入到隊列中

int size = length / config.getNthread();for(int i = 0; i < config.getNthread(); i++){int start = i * size;int len;if(i == config.getNthread() - 1)len = length - start;else len = size;//並將當前的下載加入到下載隊列中addDownloadBlock(getDownloadBlock(start, len));}


3)啟動線程進行下載

下載的步驟如下:

   1. 建立緩衝,建立串連。設定擷取資源資料的範圍,建立檔案,並設定寫入位置

//建立緩衝byte [] b;if(block.getLength() < Constants.BYTES_READ)b = new byte[(int)block.getLength()];elseb = new byte[Constants.BYTES_READ];//建立串連。設定擷取資源資料的範圍,從startPos到endPosURLConnection con = null;con.setRequestProperty("Range", "bytes=" + block.getStart() + "-" + block.getStart()+block.getLength()-1);RandomAccessFile file = new RandomAccessFile(block.getDownload().getConfig().getFile(), "rw");//建立RandomAccessFilefile.seek(block.getStart()); //從startPos開始寫入


  2. 如果當前block的length大於0,則從URL資源處擷取固定大小的資源,並將其寫入到檔案中。

  3 .更新block塊的start,以及length,如果length大於0,繼續進行2,否則則表示當前block已經下載完畢,退出該線程。

InputStream in = block.getDownload().getConfig().getUrl().openStream();int n;//對該block內的檔案進行下載,while(count < block.getLength()){if (needSplit()) { // 檢查該Block是否還需要分塊(即當前block剩餘的大小大於一次下載的量)long newLength = (block.getLength() - count) / 2;long newStart = block.getStart() + block.getLength() - newLength;DownloadBlock newBlock = block.getDownload().getDownloadBlock(newStart, newLength);block.setLength(block.getLength() - newLength);block.getDownload().addDownloadBlock(newBlock);}//寫入檔案n = in.read(b);if(n < 0){break;}else if(count + n > block.getLength()){file.write(b, 0, (int)(block.getLength() - count));count = block.getLength();}else {count += n;file.write(b, 0, n);}// set block count in downloadif(n > 0){//統計每個block中已經下載的段的個數,用於計算當前下載的速度。block.getDownload().setBlockCount(block.getStart(), count);}}in.close();file.close();


二 . 當前檔案下載速度與進度計算

如第一個圖所表示的,每個Block中又分為了很多的段D1、D2、…Dn,因此當為了計算當前下載的速度,需要將下載的段D的數量統計出來,這裡使用了一個ConcurrentHashMap<Long, Long>來儲存每個block已經下載完成的段D的數目。其中key為每個block的start值,而value為該block已經下載完的段D。
在當前時刻,我們需要統計當前Download已經下載完成段D的數量,然後再和上一時刻的相比較,則可以得出當前的下載速度。具體代碼見下:

class CheckSpeedTask extends TimerTask{private static final Log log = LogFactory.getLog(CheckSpeedTask.class);private Download download;private ConcurrentHashMap<Long, Long> blockCounts; private long speed = 0; // Byte/Sprivate long count = 0; // Total downloaded byte countprivate long lastCount = 0;private long time = 0; // Check timeprivate long lastTime = 0;public CheckSpeedTask(Download download, long startTime, ConcurrentHashMap<Long, Long> blockCounts){this.download = download;this.lastTime = startTime;this.blockCounts = blockCounts;}@Overridepublic void run() {try {time = System.currentTimeMillis();count = 0;//需要統計當前已經下載完成段D的數量。for(long c : blockCounts.values()){count += c;}speed = (count -lastCount)/((time - lastTime)/1000);log.debug(blockCounts.size() + " threads are downloading " + download + ", cuttent is " + speed + "Byte/S, " + (count * 1.0)/download.getLength()*100 + "% downloaded");download.setCount(count);download.setSpeed(speed);lastTime = time;lastCount = count;} catch (Exception e) {// TODO: handle exceptione.printStackTrace();}}}

這樣我們就可以在Thread類的run()函數中,計算當前下載的速度

while(activeThreads.size() > 0 || blockQueue.size() > 0){Thread.sleep(1000);checkSpeed();}

上面的代碼示範了如何使用Java多線程對單個檔案進行下載,接下來我們繼續討論如何對多個下載進行調度、管理等

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.