- 多线程下载
原理:服务器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 }