带断点续传的多线程下载

  • 多线程下载

原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源,所以使用多线程下载的话,速度会更快

JavaSE实现带断点续传的多线程下载步骤:

  1、发送http请求至下载地址,获取要下载的资源文件的大小

  2、根据资源文件的大小,创建一个长度一样的临时文件,用来抢占磁盘空间

  3、计算每个线程要下载的数据大小和开始位置、结束位置,余数都由最后一个线程完成下载,所以最后一个线程的结束位置要写死

  4、再次发送请求,请求要下载的数据区间的数据(判断是否有记录进度的临时文件,有的话就继续上次位置接着下载,没有就从原本开始位置下载)

  5、将下载请求到的数据,存储到临时文件中(新建一个记录下载进度的临时文件)

  6、等所有线程都下载完毕了,就要将之前的记录进度的临时文件删除掉

  1 package com.ahu.multithreaddownload;
  2 
  3 import java.io.BufferedReader;
  4 import java.io.File;
  5 import java.io.FileInputStream;
  6 import java.io.InputStream;
  7 import java.io.InputStreamReader;
  8 import java.io.RandomAccessFile;
  9 import java.net.HttpURLConnection;
 10 import java.net.URL;
 11 
 12 /**
 13  * 带断点续传的多线程下载
 14  * 
 15  * @author ahu_lichang
 16  * 
 17  */
 18 public class MultiThreadDownload {
 19     // 线程数
 20     static int ThreadCount = 4;
 21     static int finishedThread = 0;
 22 
 23     static String path = "http://172.23.13.179:8080/report.ppt";
 24 
 25     public static void main(String[] args) {
 26         try {
 27             // 第一次请求服务器,是为了获取资源文件的大小,从而创建一个相同大小的临时文件,而不是下载资源!
 28             URL url = new URL(path);
 29             HttpURLConnection connection = (HttpURLConnection) url
 30                     .openConnection();
 31             connection.setConnectTimeout(5000);
 32             connection.setReadTimeout(5000);
 33             connection.setRequestMethod("GET");
 34             //connection.connect();
 35             if (connection.getResponseCode() == 200) {
 36                 // 获取要下载文件的大小
 37                 int length = connection.getContentLength();
 38                 // 生成临时文件
 39                 RandomAccessFile raf = new RandomAccessFile(getFileName(path),
 40                         "rwd");
 41                 raf.setLength(length);
 42                 raf.close();
 43                 // 计算每个线程下载的资源大小(有可能存在余数,余数都放在最后一个线程里)
 44                 int size = length / ThreadCount;
 45                 // 计算每个线程下载的开始位置和结束位置
 46                 for (int i = 0; i < ThreadCount; i++) {
 47                     int startIndex = i * size;
 48                     int endIndex = (i + 1) * size - 1;
 49                     // 如果是最后一个线程,必须将结束位置写死
 50                     if (i == ThreadCount - 1) {
 51                         endIndex = length - 1;
 52                     }
 53                     // 开启线程下载资源
 54                     new DownloadThread(startIndex, endIndex, i).start();
 55                 }
 56             }
 57         } catch (Exception e) {
 58             e.printStackTrace();
 59         }
 60 
 61     }
 62 
 63     /**
 64      * 获取服务器中资源的名称
 65      * 
 66      * @param path
 67      * @return
 68      */
 69     public static String getFileName(String path) {
 70         int index = path.lastIndexOf("/");
 71         return path.substring(index + 1);
 72     }
 73 
 74 }
 75 /**
 76  * 下载线程
 77  * @author ahu_lichang
 78  *
 79  */
 80 class DownloadThread extends Thread {
 81     int startIndex;
 82     int endIndex;
 83     int threadId;
 84 
 85     public DownloadThread(int startIndex, int endIndex, int threadId) {
 86         super();
 87         this.startIndex = startIndex;
 88         this.endIndex = endIndex;
 89         this.threadId = threadId;
 90     }
 91 
 92     public void run() {
 93         try {
 94             File progressFile = new File(threadId + ".txt");
 95             //如果存在进度临时文件,就接着上次的后面进行下载
 96             if (progressFile.exists()) {
 97                 FileInputStream fis = new FileInputStream(progressFile);
 98                 BufferedReader br = new BufferedReader(new InputStreamReader(
 99                         fis));
100                 // 得到上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
101                 startIndex += Integer.parseInt(br.readLine());
102                 fis.close();
103             }
104             System.out.println("线程" + threadId + "下载区间" + startIndex + "---"
105                     + endIndex);
106             // 再次请求服务器,下载资源文件
107             URL url = new URL(MultiThreadDownload.path);
108             HttpURLConnection connection = (HttpURLConnection) url
109                     .openConnection();
110             connection.setConnectTimeout(5000);
111             connection.setReadTimeout(5000);
112             connection.setRequestMethod("GET");
113             // 设置本次所请求的数据区间
114             connection.setRequestProperty("Range", "bytes=" + startIndex + "-"
115                     + endIndex);
116             //connection.connect();
117             // 请求部分数据的响应码是206
118             if (connection.getResponseCode() == 206) {
119                 // 拿到1/3源文件的数据
120                 InputStream is = connection.getInputStream();
121                 byte[] b = new byte[1024];
122                 int len = 0;
123                 int total = 0;// 记录下载到临时文件中数据的大小
124                 // 把拿到的数据写入到临时文件中
125                 RandomAccessFile raf = new RandomAccessFile(
126                         MultiThreadDownload
127                                 .getFileName(MultiThreadDownload.path),
128                         "rwd");
129                 // 把文件的写入位置移动至startIndex。这样才不会覆盖写入
130                 raf.seek(startIndex);
131                 while ((len = is.read(b)) != -1) {
132                     raf.write(b, 0, len);
133                     total += len;
134                     // 生成用来记录下载进度的临时文件
135                     RandomAccessFile progressRaf = new RandomAccessFile(
136                             progressFile, "rwd");
137                     progressRaf.write((total + "").getBytes());
138                     progressRaf.close();
139                 }
140                 System.out.println("线程" + threadId + "下载完毕!!!");
141                 raf.close();
142 
143                 // 全部下载完成后,将临时存放进度的文件删除
144                 MultiThreadDownload.finishedThread++;
145                 // 注意线程安全问题
146                 synchronized (MultiThreadDownload.path) {
147                     if (MultiThreadDownload.finishedThread == MultiThreadDownload.ThreadCount) {
148                         for (int i = 0; i < MultiThreadDownload.ThreadCount; i++) {
149                             File f = new File(i + ".txt");
150                             f.delete();
151                         }
152                         MultiThreadDownload.finishedThread = 0;
153                     }
154                 }
155             }
156         } catch (Exception e) {
157             e.printStackTrace();
158         }
159     }
160 }
View Code

 

  • Android上实现带断点续传的多线程下载

布局文件:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:id="@+id/activity_main"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     android:paddingBottom="@dimen/activity_vertical_margin"
 8     android:paddingLeft="@dimen/activity_horizontal_margin"
 9     android:paddingRight="@dimen/activity_horizontal_margin"
10     android:paddingTop="@dimen/activity_vertical_margin"
11     tools:context="com.ahu.lichang.multithreaddownload.MainActivity"
12     android:orientation="vertical">
13     <Button
14         android:text="多线程下载"
15         android:onClick="download"
16         android:layout_width="wrap_content"
17         android:layout_height="wrap_content" />
18 
19     <ProgressBar
20         android:id="@+id/pb"
21         style="@android:style/Widget.ProgressBar.Horizontal"
22         android:layout_width="match_parent"
23         android:layout_height="wrap_content" />
24     <TextView
25         android:id="@+id/tv"
26         android:layout_width="wrap_content"
27         android:layout_height="wrap_content"
28         android:text="下载进度:0%" />
29 
30 </LinearLayout>
View Code

   MainActivity:

  1 package com.ahu.lichang.multithreaddownload;
  2 
  3 import android.app.Activity;
  4 import android.os.Bundle;
  5 import android.os.Environment;
  6 import android.os.Handler;
  7 import android.view.View;
  8 import android.widget.ProgressBar;
  9 import android.widget.TextView;
 10 
 11 import java.io.BufferedReader;
 12 import java.io.File;
 13 import java.io.FileInputStream;
 14 import java.io.InputStream;
 15 import java.io.InputStreamReader;
 16 import java.io.RandomAccessFile;
 17 import java.net.HttpURLConnection;
 18 import java.net.URL;
 19 
 20 public class MainActivity extends Activity {
 21     static int ThreadCount = 3;
 22     static int finishedThread = 0;
 23 
 24     int currentProgress;
 25     String fileName = "QQPlayer.exe";
 26     //确定下载地址
 27     String path = "http://172.23.13.179:8080/" + fileName;
 28     private ProgressBar pb;
 29     TextView tv;
 30 
 31     Handler handler = new Handler(){
 32         public void handleMessage(android.os.Message msg) {
 33             //把变量改成long,在long下运算
 34             tv.setText((long)pb.getProgress() * 100 / pb.getMax() + "%");
 35         }
 36     };
 37     @Override
 38     protected void onCreate(Bundle savedInstanceState) {
 39         super.onCreate(savedInstanceState);
 40         setContentView(R.layout.activity_main);
 41         pb = (ProgressBar) findViewById(R.id.pb);
 42         tv = (TextView) findViewById(R.id.tv);
 43     }
 44     public void download(View view){
 45         Thread t = new Thread(){
 46             @Override
 47             public void run() {
 48                 //发送get请求,请求这个地址的资源
 49                 try {
 50                     URL url = new URL(path);
 51                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 52                     conn.setRequestMethod("GET");
 53                     conn.setConnectTimeout(5000);
 54                     conn.setReadTimeout(5000);
 55 
 56                     if(conn.getResponseCode() == 200){
 57                         //拿到所请求资源文件的长度
 58                         int length = conn.getContentLength();
 59 
 60                         //设置进度条的最大值就是原文件的总长度
 61                         pb.setMax(length);
 62 
 63                         File file = new File(Environment.getExternalStorageDirectory(), fileName);
 64                         //生成临时文件
 65                         RandomAccessFile raf = new RandomAccessFile(file, "rwd");
 66                         //设置临时文件的大小
 67                         raf.setLength(length);
 68                         raf.close();
 69                         //计算出每个线程应该下载多少字节
 70                         int size = length / ThreadCount;
 71                         for (int i = 0; i < ThreadCount; i++) {
 72                             //计算线程下载的开始位置和结束位置
 73                             int startIndex = i * size;
 74                             int endIndex = (i + 1) * size - 1;
 75                             //如果是最后一个线程,那么结束位置写死
 76                             if(i == ThreadCount - 1){
 77                                 endIndex = length - 1;
 78                             }
 79                             new DownLoadThread(startIndex, endIndex, i).start();
 80                         }
 81                     }
 82                 } catch (Exception e) {
 83                     e.printStackTrace();
 84                 }
 85             }
 86         };
 87         t.start();
 88     }
 89     class DownLoadThread extends Thread{
 90         int startIndex;
 91         int endIndex;
 92         int threadId;
 93 
 94         public DownLoadThread(int startIndex, int endIndex, int threadId) {
 95             super();
 96             this.startIndex = startIndex;
 97             this.endIndex = endIndex;
 98             this.threadId = threadId;
 99         }
100 
101         @Override
102         public void run() {
103             //再次发送http请求,下载原文件
104             try {
105                 File progressFile = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
106                 //判断进度临时文件是否存在
107                 if(progressFile.exists()){
108                     FileInputStream fis = new FileInputStream(progressFile);
109                     BufferedReader br = new BufferedReader(new InputStreamReader(fis));
110                     //从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
111                     int lastProgress = Integer.parseInt(br.readLine());
112                     startIndex += lastProgress;
113 
114                     //把上次下载的进度显示至进度条
115                     currentProgress += lastProgress;
116                     pb.setProgress(currentProgress);
117                     //发送消息,让主线程刷新文本进度
118                     handler.sendEmptyMessage(1);
119 
120                     fis.close();
121                 }
122                 System.out.println("线程" + threadId + "的下载区间是:" + startIndex + "---" + endIndex);
123                 HttpURLConnection conn;
124                 URL url = new URL(path);
125                 conn = (HttpURLConnection) url.openConnection();
126                 conn.setRequestMethod("GET");
127                 conn.setConnectTimeout(5000);
128                 conn.setReadTimeout(5000);
129                 //设置本次http请求所请求的数据的区间
130                 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
131 
132                 //请求部分数据,相应码是206
133                 if(conn.getResponseCode() == 206){
134                     //流里此时只有1/3原文件的数据
135                     InputStream is = conn.getInputStream();
136                     byte[] b = new byte[1024];
137                     int len = 0;
138                     int total = 0;
139                     //拿到临时文件的输出流
140                     File file = new File(Environment.getExternalStorageDirectory(), fileName);
141                     RandomAccessFile raf = new RandomAccessFile(file, "rwd");
142                     //把文件的写入位置移动至startIndex
143                     raf.seek(startIndex);
144                     while((len = is.read(b)) != -1){
145                         //每次读取流里数据之后,同步把数据写入临时文件
146                         raf.write(b, 0, len);
147                         total += len;
148                         System.out.println("线程" + threadId + "下载了" + total);
149 
150                         //每次读取流里数据之后,把本次读取的数据的长度显示至进度条
151                         currentProgress += len;
152                         pb.setProgress(currentProgress);
153                         //发送消息,让主线程刷新文本进度
154                         handler.sendEmptyMessage(1);
155 
156                         //生成一个专门用来记录下载进度的临时文件
157                         RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
158                         //每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中
159                         progressRaf.write((total + "").getBytes());
160                         progressRaf.close();
161                     }
162                     System.out.println("线程" + threadId + "下载完毕");
163                     raf.close();
164 
165                     finishedThread++;
166                     synchronized (path) {
167                         if(finishedThread == ThreadCount){
168                             for (int i = 0; i < ThreadCount; i++) {
169                                 File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
170                                 f.delete();
171                             }
172                             finishedThread = 0;
173                         }
174                     }
175                 }
176             } catch (Exception e) {
177                 e.printStackTrace();
178             }
179         }
180     }
181 }
View Code
1     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
2     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3     <uses-permission android:name="android.permission.INTERNET"/>

 

 

 

posted @ 2017-03-21 14:01  ahu-lichang  阅读(397)  评论(0编辑  收藏  举报