Java多線程文件分片下載實(shí)現(xiàn)的示例代碼
多線程下載介紹
多線程下載技術(shù)是很常見的一種下載方案,這種方式充分利用了多線程的優(yōu)勢,在同一時(shí)間段內(nèi)通過多個(gè)線程發(fā)起下載請求,將需要下載的數(shù)據(jù)分割成多個(gè)部分,每一個(gè)線程只負(fù)責(zé)下載其中一個(gè)部分,然后將下載后的數(shù)據(jù)組裝成完整的數(shù)據(jù)文件,這樣便大大加快了下載效率。常見的下載器,迅雷,QQ旋風(fēng)等都采用了這種技術(shù)。
分片下載
所謂分片下載就是要利用多線程的優(yōu)勢,將要下載的文件一塊一塊的分配到各個(gè)線程中去下載,這樣就極大的提高了下載速度。
技術(shù)難點(diǎn)
并不能說是什么難點(diǎn),只能說沒接觸過不知道罷了。
1、如何請求才能拿到數(shù)據(jù)的特定部分,而非全部?
可以在HTTP請求頭中加入Range來標(biāo)識數(shù)據(jù)的請求范圍/區(qū)間,從HTTP/1.1開始可用。
基本用法:
Range: bytes=10-:取第10個(gè)字節(jié)及后所有數(shù)據(jù)。
Range: bytes=40-100:取第40個(gè)字節(jié)到第100個(gè)字節(jié)之間的數(shù)據(jù)。
這樣我們就能拿到特定部分的數(shù)據(jù)了,斷點(diǎn)續(xù)傳也可以用這個(gè)來實(shí)現(xiàn)。
PS:0為開始點(diǎn)。
2、分片后某線程下載時(shí)如何寫出?
思路1:等所有下載完成后進(jìn)行統(tǒng)一匯總整理然后再一次性寫出。
這簡直是最笨的思路了,如果文件過大全部拉到內(nèi)存中,豈不涼涼。
思路2:下載采用多線程,寫出時(shí)采取數(shù)據(jù)前后順序排隊(duì)寫出。
也就是說多線程下載,單線程輸出,某種程度解決了內(nèi)存占用問題,不過效率基本不理想。
思路3:要說還是API香,老大哥Java給我們提供了一個(gè)類叫做RandomAccessFile。
這個(gè)類可以進(jìn)行隨機(jī)文件讀寫,其中有一個(gè)seek函數(shù),可以將指針指向任意位置,然后進(jìn)行讀寫。什么意思呢,舉個(gè)栗子:假如我們開了30個(gè)線程,首先第一個(gè)下載完成的是線程X,它下載的數(shù)據(jù)范圍是4000-9000,那么這時(shí)我們調(diào)用seek函數(shù)將指針撥動(dòng)到4000,然后調(diào)用它的write函數(shù)將byte寫出,這時(shí)4000之前都是NULL,4000之后就是我們插入的數(shù)據(jù)。這樣就可以實(shí)現(xiàn)多線程下載和本地寫入了。
具體實(shí)現(xiàn)
一個(gè)分片下載類,我們需要?jiǎng)?chuàng)建多個(gè)對象來進(jìn)行下載。
public class UnitDownloader implements Runnable {
private int from;
private int to;
private File target;
private String uri;
private int id;
public UnitDownloader(int from, int to, File target, String uri, int id) {
this.from = from;
this.to = to;
this.target = target;
this.uri = uri;
this.id = id;
}
public int getFrom() {
return from;
}
public int getTo() {
return to;
}
@Override
public void run() {
//download and save data
try {
HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
connection.setRequestProperty("Range", "bytes=" + from + "-" + to);
connection.connect();
int totalSize = connection.getContentLength();
InputStream inputStream = connection.getInputStream();
RandomAccessFile randomAccessFile = new RandomAccessFile(target, "rw");
randomAccessFile.seek(from);
byte[] buffer = new byte[1024 * 1024];
int readCount = inputStream.read(buffer, 0, buffer.length);
while (readCount > 0) {
totalSize -= readCount;
System.out.println("分片:" + this.id + "的剩余:" + totalSize);
randomAccessFile.write(buffer, 0, readCount);
readCount = inputStream.read(buffer, 0, buffer.length);
}
inputStream.close();
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
分片下載管理器,主要就是拿到內(nèi)容的總大小,將其分配給每一個(gè)UnitDownloader。這里的threadCount函數(shù)可以再考慮優(yōu)化一下。
public class MultipleThreadDownloadManager implements Runnable {
private String uri;
private File target;
public MultipleThreadDownloadManager(String uri, File target) {
this.target = target;
this.uri = uri;
if (target.exists() == false) {
try {
target.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 開始下載
*/
public void start() {
new Thread(this).start();
}
/**
* 根據(jù)文件總大小計(jì)算線程數(shù)量
*
* @param totalSize
* @return
*/
public int threadCount(int totalSize) {
if (totalSize < 30 * 2014 * 1024) {
return 1;
}
return 30;
}
@Override
public void run() {
//獲取文件總大小
int totalSize = 0;
try {
HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
connection.connect();
int contentLength = connection.getContentLength();
totalSize = contentLength;
} catch (IOException e) {
e.printStackTrace();
}
//將文件分片并分開下載
int threadCount = threadCount(totalSize);
int perThreadSize = totalSize / threadCount;//每一個(gè)線程分到的任務(wù)下載量
int id = 0;
int from = 0, to = 0;
while (totalSize > 0) {
id++;
//計(jì)算分片
if (totalSize < perThreadSize) {
from = 0;
to = totalSize;
} else {
from = totalSize;
to = from + perThreadSize;
}
//開始下載
UnitDownloader downloader = new UnitDownloader(from, to, target, uri, id);
new Thread(downloader).start();
}
}
}
參考文獻(xiàn)
2、https://blog.csdn.net/lyt_7cs1dn9/article/details/75105266
到此這篇關(guān)于Java多線程文件分片下載實(shí)現(xiàn)的示例代碼的文章就介紹到這了,更多相關(guān)Java多線程分片下載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過實(shí)例解析spring bean之間的關(guān)系
這篇文章主要介紹了通過實(shí)例解析spring bean之間的關(guān)系,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能
最近有一個(gè)需求是關(guān)于視頻上傳播放的,需要設(shè)計(jì)一個(gè)方案,中間談到了Minio這個(gè)技術(shù),于是來學(xué)習(xí)一下,所以本文給大家介紹了SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2024-07-07
Java 中jasperReport實(shí)現(xiàn)動(dòng)態(tài)列打印的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java 中jasperReport實(shí)現(xiàn)動(dòng)態(tài)列打印的實(shí)現(xiàn)代碼的相關(guān)資料,希望通過本文大家能掌握這部分內(nèi)容,需要的朋友可以參考下2017-09-09
一文詳解Elasticsearch和MySQL之間的數(shù)據(jù)同步問題
Elasticsearch中的數(shù)據(jù)是來自于Mysql數(shù)據(jù)庫的,因此當(dāng)數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行增刪改后,Elasticsearch中的數(shù)據(jù),索引也必須跟著做出改變。本文主要來和大家探討一下Elasticsearch和MySQL之間的數(shù)據(jù)同步問題,感興趣的可以了解一下2023-04-04
Java?IO流與NIO技術(shù)綜合應(yīng)用詳細(xì)實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Java?IO流與NIO技術(shù)綜合應(yīng)用的相關(guān)資料,文中包括了字節(jié)流和字符流,以及它們的高級特性如緩沖區(qū)、序列化和反序列化,同時(shí)還介紹了NIO中的通道和緩沖區(qū),以及選擇器的使用,需要的朋友可以參考下2024-12-12
Java設(shè)計(jì)模式之建造者模式實(shí)例詳解
這篇文章主要介紹了Java設(shè)計(jì)模式之建造者模式,結(jié)合具體實(shí)例形式分析了建造者模式的概念、原理、實(shí)現(xiàn)方法與相關(guān)使用注意事項(xiàng),需要的朋友可以參考下2017-09-09
Spring加載屬性文件方式(自動(dòng)加載優(yōu)先級問題)
這篇文章主要介紹了Spring加載屬性文件方式(自動(dòng)加載優(yōu)先級問題),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02

