1. 为什么需要多线程下载?
服务器的资源有限,同时的平均地分配给每个客户端。开启的线程越多抢占的服务的资源就越多,下载的速度就越块。
2. 下载速度的限制条件?
(1)你的电脑手机宽带的带宽。(网络运营商给用户的限制)
(2)服务器上传的带宽限制。 (服务器端资源获取速度的限制)----迅雷, p2p快播等下载,可以同时间使用多台服务器帮助用户下载资源,速度自然会加快。
注意:并不是开的线程越多下载速度越快,可能会消耗大量时间在线程调度上。
Android下推荐开启:3 ~ 5线程。
3. 如何进行多线程的下载:
(1)在客户端本地创建一个空文件(申请一块内存),大小要和服务器上要下载的资源一样.
(2)开启3个线程,都去下载服务器的数据.
(3)当三个线程都工作完毕后,多线程的下载就结束了.
这里特别注意最后一个线程需要修正,主要是因为不可能实现完全等分,具体如下:
4.JavaSE代码实现多线程下载:
(1)我们可以先编写java项目工程,调试实现多线程下载逻辑类MutilDownloader.java:
(2)打开Apache服务器,在相应的目录下存放测试下载文件,如下:
(3)MutilDownloader.java,如下:
1 package com.himi.mutildownload; 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 */ 16 public class MutilDownloader { 17 /** 18 * Apache服务器上资源下载的路径 19 */ 20 private static final String path = "http://49.123.76.170/movies/test.avi"; 21 /** 22 * 多少个线程去下载服务器的资源 23 */ 24 private static int threadCount = 4; 25 26 /** 27 * 正在运行的线程的数量 28 */ 29 private static int runningThreadCount; 30 31 public static void main(String[] args) throws Exception { 32 URL url = new URL(path); 33 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 34 conn.setRequestMethod("GET"); 35 int code = conn.getResponseCode(); 36 if (code == 200) { 37 int length = conn.getContentLength(); 38 System.out.println("服务器文件的大小为:" + length); 39 40 // 1. 创建一个空白文件文件的大小和服务器资源一样 41 RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw"); 42 raf.setLength(length); 43 raf.close(); 44 45 // 每个线程下载的平均区块大小 46 int blocksize = length / threadCount; 47 System.out.println("每一份:" + blocksize); 48 49 runningThreadCount = threadCount; 50 // 2. 开启3个线程,都去下载服务器的对应数据 51 for (int threadId = 0; threadId < threadCount; threadId++) { 52 int startIndex = threadId * blocksize; 53 int endIndex = (threadId + 1) * blocksize - 1; 54 55 // 最后一个线程的修正,最后一个线程endIndex设置为文件末尾 56 if (threadId == (threadCount - 1)) { 57 endIndex = length - 1;// 文件byte是从0开始计数的 58 } 59 60 new DownloadThread(startIndex, endIndex, threadId).start(); 61 } 62 } 63 64 // 3. 当三个线程都工作完毕后,多线程的下载就结束了. 65 66 } 67 68 public static class DownloadThread extends Thread { 69 /** 70 * 线程id 71 */ 72 int threadId; 73 74 /** 75 * 当前线程下载的开始位置 76 */ 77 int startIndex; 78 /** 79 * 当前线程下载的结束位置 80 */ 81 int endIndex; 82 83 /** 84 * 当前线程下载到文件的位置 85 */ 86 int filePosition; 87 88 /** 89 * 90 * @param startIndex 91 * 开始位置 92 * @param endIndex 93 * 结束位置 94 * @param threadId 95 * 线程id 96 */ 97 public DownloadThread(int startIndex, int endIndex, int threadId) { 98 this.startIndex = startIndex; 99 this.endIndex = endIndex; 100 this.threadId = threadId; 101 filePosition = startIndex; 102 } 103 104 @Override 105 public void run() { 106 try { 107 // 用一个文本记录当前线程下载的进程 108 File file = new File(threadId + getFileName(path) + ".txt"); 109 110 if (file.exists() && file.length() > 0) { 111 FileInputStream fis = new FileInputStream(file); 112 BufferedReader br = new BufferedReader(new InputStreamReader(fis)); 113 filePosition = Integer.parseInt(br.readLine());// 上一次下载到文件的哪个位子。 114 startIndex = filePosition; 115 fis.close(); 116 } 117 118 System.out.println("线程:" + threadId + "实际上下载的位置:" + startIndex + "~~~" + endIndex); 119 120 URL url = new URL(path); 121 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 122 conn.setRequestMethod("GET"); 123 // 指定从服务器下载的范围,http请求的头 124 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); 125 126 int code = conn.getResponseCode();// 2XX成功 3XX重定向 4XX资源找不到 127 // 5XX服务器异常 128 if (code == 206) {// 206:表示请求部分数据成功 129 // 返回服务器端对应数据的输入流 130 InputStream is = conn.getInputStream(); 131 RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd"); 132 133 /** 134 * ☆☆☆☆☆非常重要☆☆☆☆☆ 一定要记得定位文件写的位置 135 * 不同线程在文件中(代码开头:创建的空文件)开始写入的位置是不一样的 136 */ 137 raf.seek(startIndex); 138 byte[] buffer = new byte[1024 * 1024 * 10]; 139 int len = -1; 140 while ((len = is.read(buffer)) != -1) { 141 raf.write(buffer, 0, len); 142 filePosition += len; 143 RandomAccessFile rafinfo = new RandomAccessFile(file, "rwd"); 144 rafinfo.write(String.valueOf(filePosition).getBytes()); 145 rafinfo.close(); 146 } 147 raf.close(); 148 is.close(); 149 150 System.out.println("线程:" + threadId + "下载完毕了。"); 151 152 } 153 } catch (Exception e) { 154 e.printStackTrace(); 155 } finally { 156 157 // 三个线程都结束了,下载完毕 158 synchronized (MutilDownloader.class) { 159 runningThreadCount--; 160 if (runningThreadCount == 0) { 161 System.out.println("所有的线程都下载完毕了"); 162 for (int i = 0; i < threadCount; i++) { 163 File f = new File(i + getFileName(path) + ".txt"); 164 System.out.println(f.delete()); 165 } 166 } 167 } 168 } 169 } 170 } 171 172 /** 173 * 获取路径对应的文件名 174 * 175 * @param path 176 * @return 177 */ 178 private static String getFileName(String path) { 179 int beginIndex = path.lastIndexOf("/") + 1; 180 return path.substring(beginIndex); 181 } 182 183 }
需要特别注意的是:RandomAccessFile.seek(startIndex),它是用来定位文件写入的位置。
运行程序,观察Console,如下:
刷新Java工程项目,如下:
双击打开test.avi,发现是可以播放的。