下載文件的時候,一個大文件切成很多片,用多線程下載,速度會快很多
閱讀代碼的時候注意查看代碼里面的注釋
想用多線程下載文件,則,
第一:得了解 RandomAccessFile 類,這是個隨機訪問文件類,里面可以設置 訪問的 開始地址和結束地址,且該類可讀可寫。
RandomAccessFile out = new RandomAccessFile(file, "rw"); 則表示,該類可讀可寫。通過 out.seek(start) 可以定位開始讀取的位置。
第二:既然是網絡文件下載,那就必須得了解 URL 類,該類是 java.net 包提供的一個 可以用來網絡連接的類。
URL url = new URL(urlLocation); 可以這樣實例化該類,然后打開連接,HttpURLConnection conn = (HttpURLConnection) url.openConnection();,還可以設置些別的參數,比如說設置超時,設置訪問方法,設置 訪問 起始點之類的。
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
conn.setRequestProperty("Range", "bytes=" + start +"-"+end );
第三:了解線程,這里我們使用 java 1.5之后引入的 concurrent 包里面的 Executors.newCachedThreadPool() 線程池
第四:最基本的 io讀寫得知道
看看代碼吧。
1.為了方便,我寫了個工具類,用於提供 Util類用來提供獲取 HttpURLConnection 連接
public class Util {
// 記錄讀取了多少,一共讀取了多少
public static long start;
// 記錄文件總大小
public static long sum;
/**
*
* @Title: getHttpConnection
* @Description: 獲取 url 連接
* @param: @param urlLocation
* @param: @return HttpURLConnection實例化對象
* @param: @throws IOException
* @return: HttpURLConnection
* @throws
*/
public static HttpURLConnection getHttpConnection(String urlLocation) throws IOException {
URL url = new URL(urlLocation);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
return conn;
}
}
2.文件切片,分別指定 起始點,注意這里的起始點是包頭也包尾的,0-10/11-20/21-30 這種
public class DownloadFilePool {
// 網絡資源路徑
private String urlLocation;
// 存儲路徑
private String filePath;
// 多少個線程
private int poolLength;
public DownloadFilePool(String urlLocation, String filePath, int poolLength) {
super();
// 如果 保存路徑為空則默認存在 D盤,文件名跟下載名相同
if( filePath==null ) {
String fileName = urlLocation.substring( urlLocation.lastIndexOf("/") +1);
filePath = "D:/" + fileName;
}
this.urlLocation = urlLocation;
this.filePath = filePath;
this.poolLength = poolLength;
}
public void getFile() {
try {
// 獲取文件長度
long fileLength = Util.getHttpConnection(urlLocation).getContentLengthLong();
Util.sum = fileLength;
ExecutorService pool = Executors.newCachedThreadPool();
// 獲取每片大小
long slice = fileLength/poolLength;
for(int i = 0 ;i < poolLength; i++) {
long start = i*slice;
long end = (i+1)*slice -1;
if(i==poolLength-1) {
start = i*slice;
end = fileLength ;
}
System.out.println( start + "---" + end );
// 創建下載類
DownloadFileRang downloadFileRang = new DownloadFileRang(start, end, urlLocation, filePath);
// 執行線程
pool.execute(downloadFileRang);
}
// 關閉線程池
pool.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.文件下載類,此處使用 繼承 Runnable 實現多線程
public class DownloadFileRang implements Runnable {
// 文件開始位置
private long start ;
// 文件結束位置
private long end;
// url地址
private String urlLocation;
// 文件存儲位置
private String filePath;
public DownloadFileRang(long start, long end, String urlLocation, String filePath) {
super();
this.start = start;
this.end = end;
this.urlLocation = urlLocation;
this.filePath = filePath;
}
@Override
public void run() {
try {
// 獲取連接
HttpURLConnection conn = Util.getHttpConnection(urlLocation);
// 設置獲取資源范圍
conn.setRequestProperty("Range", "bytes=" + start +"-"+end );
File file = new File(filePath);
RandomAccessFile out = null;
if(file!=null) {
out = new RandomAccessFile(file, "rw");
}
out.seek(start);
// 獲取網絡連接的 輸入流
InputStream is = conn.getInputStream();
byte [] data = new byte[1024];
int len = 0;
while( (len = is.read(data))!=-1 ) {
out.write(data, 0, len);
synchronized (Util.class) {
Util.start += len;
}
}
// 關閉連接
out.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.實現顯示實時網速
實時網速,其實就是單位時間內,所讀取到的 資源,我們這里就是讀取到的 len,觀察我的代碼,可以發現,我在 Util類里面寫了兩個靜態屬性,分別是 start 用來記錄一共讀了多少,一個是 sum,記錄總文件大小。
線程類里面的 out.write 方法里面我 用 start 進行累加 len,這就是記錄一共讀取了多少資源,因為 多線程不能保證數據的原子性,所以我這里累加的時候,為避免因為線程原因出現數據錯誤,則進行加鎖,加上 Util 對象的鎖,告訴別的線程,這個 strat 同意時刻內只能一個線程來進行 操作。然后 用 每個時間段,比如說我這里是 500ms ,內讀取的資源 / 500ms 就是網速了。
5.調用實例
public static void main(String[] args) {
Date startDate = new Date();
DownloadFilePool pool = new DownloadFilePool("http://fs.w.kugou.com/201809152325/5cbbb70b45431a17cad6ddd6d5342ef5/G108/M03/0C/01/rA0DAFk_VuiALN9DADmXB0zHYTA058.mp3", null, 100);
pool.getFile();
long old = 0;
long now = 0;
while( Util.sum >= Util.start ) {
now = Util.start - old;
old = Util.start;
if(Util.sum == Util.start) {
long t = new Date().getTime() - startDate.getTime();
double speed = ((double)Util.sum / (t/1000.0))/1024.0/1024.0;
System.out.println( "下載完成,用時:" + t/1000.0 +" s 平均網速:" + speed +" M/s" );
break;
}
System.out.println( "網速:" + ((double)(now/0.5))/1024.0/1024.0 +" M/s,已完成:" + (Util.start / (double)Util.sum)*100 +"%" );
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行效果:
---------------------
作者:臨窗,聽雨聲
來源:CSDN
原文:https://blog.csdn.net/yali_aini/article/details/82713368
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!