java实现大文件下载,基于http方式,控件神马的就不说了。
思路:下载文件无非要读取文件然后写文件,主要这两个步骤,主要难点:
1.读文件,就是硬盘到内存的过程,由于jdk内存限制,不能读的太大。
2.写文件,就是响应到浏览器端的过程,http协议是短链接,如果写文件太慢,时间过久,会造成浏览器死掉。
知识点:
1.org.apache.http.impl.client.CloseableHttpClient 模拟httpClient客户端发送http请求,可以控制到请求文件的字节位置。
2.BufferedInputStream都熟悉,用它接受请求来的流信息缓存。
3.RandomAccessFile文件随机类,可以向文件写入指定位置的流信息。
基于以上信息,我的实现思路就是首先判断下载文件大小,配合多线程分割定制http请求数量和请求内容,响应到写入到RandomAccessFile指定位置中。在俗点就是大的http分割成一个个小的http请求,相当于每次请求一个网页。
废话不说,上代码。
DownLoadManagerTest类:
1package xxxx; 2 3import java.io.File; 4import java.io.IOException; 5import java.io.RandomAccessFile; 6import java.net.HttpURLConnection; 7import java.net.URL; 8import java.util.concurrent.CountDownLatch; 9import org.apache.commons.lang.exception.ExceptionUtils; 10import org.apache.http.impl.client.CloseableHttpClient; 11import org.apache.http.impl.client.HttpClients; 12import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 13import org.junit.After; 14import org.junit.Before; 15import org.junit.Test; 16import org.junit.runner.RunWith; 17import org.slf4j.Logger; 18import org.slf4j.LoggerFactory; 19import org.springframework.beans.factory.annotation.Autowired; 20import org.springframework.core.task.TaskExecutor; 21import org.springframework.test.context.ActiveProfiles; 22import org.springframework.test.context.ContextConfiguration; 23import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; 24import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 25 26/** 27 * 28 * 文件下载管理类 29 */ 30 31@RunWith(SpringJUnit4ClassRunner.class) 32@ActiveProfiles("test") 33@ContextConfiguration(locations={"classpath:test/applicationContext.xml"}) 34public class DownLoadManagerTest extends AbstractTransactionalJUnit4SpringContextTests{ 35 36 private static final Logger LOGGER = LoggerFactory.getLogger(DownLoadManagerTest.class); 37 38 /** 39 * 40 * 每个线程下载的字节数 41 */ 42 43 private long unitSize = 1000 * 1024; 44 45 @Autowired 46 private TaskExecutor taskExecutor; 47 48 private CloseableHttpClient httpClient; 49 50 private Long starttimes; 51 52 private Long endtimes; 53 54 @Before 55 public void setUp() throws Exception 56 { 57 starttimes = System.currentTimeMillis(); 58 System.out.println("测试开始...."); 59 } 60 61 @After 62 public void tearDown() throws Exception 63 { 64 endtimes = System.currentTimeMillis(); 65 System.out.println("测试结束!!"); 66 System.out.println("********************"); 67 System.out.println("下载总耗时:"+(endtimes-starttimes)/1000+"s"); 68 System.out.println("********************"); 69 } 70 71 public DownLoadManagerTest() { 72 73 System.out.println("初始化测试类...."); 74 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); 75 cm.setMaxTotal(100); 76 httpClient = HttpClients.custom().setConnectionManager(cm).build(); 77 78 } 79 80 /** 81 * 82 * 启动多个线程下载文件 83 */ 84 @Test 85 public void doDownload() throws IOException { 86 87 String remoteFileUrl="http://{host}:{port}/{project}/xx.xml"; 88 String localPath="E://test//"; 89 90 String fileName = new URL(remoteFileUrl).getFile(); 91 92 System.out.println("远程文件名称:"+fileName); 93 fileName = fileName.substring(fileName.lastIndexOf("/") + 1, 94 fileName.length()).replace("%20", " "); 95 System.out.println("本地文件名称:"+fileName); 96 long fileSize = this.getRemoteFileSize(remoteFileUrl); 97 98 this.createFile(localPath+System.currentTimeMillis()+fileName, fileSize); 99 100 Long threadCount = (fileSize/unitSize)+(fileSize % unitSize!=0?1:0); 101 long offset = 0; 102 103 CountDownLatch end = new CountDownLatch(threadCount.intValue()); 104 105 if (fileSize <= unitSize) {// 如果远程文件尺寸小于等于unitSize 106 107 DownloadThreadTest downloadThread = new DownloadThreadTest(remoteFileUrl, 108 109 localPath+fileName, offset, fileSize,end,httpClient); 110 111 taskExecutor.execute(downloadThread); 112 113 } else {// 如果远程文件尺寸大于unitSize 114 115 for (int i = 1; i < threadCount; i++) { 116 117 DownloadThreadTest downloadThread = new DownloadThreadTest( 118 119 remoteFileUrl, localPath+fileName, offset, unitSize,end,httpClient); 120 121 taskExecutor.execute(downloadThread); 122 123 offset = offset + unitSize; 124 125 } 126 127 if (fileSize % unitSize != 0) {// 如果不能整除,则需要再创建一个线程下载剩余字节 128 129 DownloadThreadTest downloadThread = new DownloadThreadTest(remoteFileUrl, localPath+fileName, offset, fileSize - unitSize * (threadCount-1),end,httpClient); 130 taskExecutor.execute(downloadThread); 131 } 132 133 } 134 try { 135 end.await(); 136 } catch (InterruptedException e) { 137 LOGGER.error("DownLoadManager exception msg:{}",ExceptionUtils.getFullStackTrace(e)); 138 e.printStackTrace(); 139 } 140// System.out.println("111111"); 141 LOGGER.debug("下载完成!{} ",localPath+fileName); 142 //return localPath+fileName; 143 } 144 145 /** 146 * 147 * 获取远程文件尺寸 148 */ 149 150 private long getRemoteFileSize(String remoteFileUrl) throws IOException { 151 152 long fileSize = 0; 153 154 HttpURLConnection httpConnection = (HttpURLConnection) new URL( 155 156 remoteFileUrl).openConnection(); 157 158 httpConnection.setRequestMethod("HEAD"); 159 160 int responseCode = httpConnection.getResponseCode(); 161 162 if (responseCode >= 400) { 163 164 LOGGER.debug("Web服务器响应错误!"); 165 166 return 0; 167 168 } 169 170 String sHeader; 171 172 for (int i = 1;; i++) { 173 174 sHeader = httpConnection.getHeaderFieldKey(i); 175 176 if (sHeader != null && sHeader.equals("Content-Length")) { 177 178 System.out.println("文件大小ContentLength:" 179 + httpConnection.getContentLength()); 180 181 fileSize = Long.parseLong(httpConnection 182 .getHeaderField(sHeader)); 183 184 break; 185 186 } 187 188 } 189 190 return fileSize; 191 192 } 193 194 /** 195 * 196 * 创建指定大小的文件 197 */ 198 199 private void createFile(String fileName, long fileSize) throws IOException { 200 201 File newFile = new File(fileName); 202 203 RandomAccessFile raf = new RandomAccessFile(newFile, "rw"); 204 205 raf.setLength(fileSize); 206 207 raf.close(); 208 209 } 210 211 212 public TaskExecutor getTaskExecutor() { 213 return taskExecutor; 214 } 215 216 public void setTaskExecutor(TaskExecutor taskExecutor) { 217 this.taskExecutor = taskExecutor; 218 } 219 220}
DownloadThreadTest类:
1package xxxx; 2 3import java.io.BufferedInputStream; 4import java.io.File; 5import java.io.IOException; 6import java.io.RandomAccessFile; 7import java.util.concurrent.CountDownLatch; 8import org.apache.commons.lang.exception.ExceptionUtils; 9import org.apache.http.client.ClientProtocolException; 10import org.apache.http.client.methods.CloseableHttpResponse; 11import org.apache.http.client.methods.HttpGet; 12import org.apache.http.impl.client.CloseableHttpClient; 13import org.apache.http.protocol.BasicHttpContext; 14import org.apache.http.protocol.HttpContext; 15import org.slf4j.Logger; 16import org.slf4j.LoggerFactory; 17 18/** 19 * 20 * 负责文件下载的类 21 */ 22 23public class DownloadThreadTest extends Thread { 24 25 private static final Logger LOGGER = LoggerFactory 26 .getLogger(DownloadThreadTest.class); 27 28 /** 29 * 30 * 待下载的文件 31 */ 32 33 private String url = null; 34 35 /** 36 * 37 * 本地文件名 38 */ 39 40 private String fileName = null; 41 42 /** 43 * 44 * 偏移量 45 */ 46 47 private long offset = 0; 48 49 /** 50 * 51 * 分配给本线程的下载字节数 52 */ 53 54 private long length = 0; 55 56 private CountDownLatch end; 57 58 private CloseableHttpClient httpClient; 59 60 private HttpContext context; 61 62 /** 63 * 64 * @param url 65 * 下载文件地址 66 * 67 * @param fileName 68 * 另存文件名 69 * 70 * @param offset 71 * 本线程下载偏移量 72 * 73 * @param length 74 * 本线程下载长度 75 * 76 * 77 * 78 * @author Angus.wang 79 * 80 * */ 81 82 public DownloadThreadTest(String url, String file, long offset, long length, 83 CountDownLatch end, CloseableHttpClient httpClient) { 84 85 this.url = url; 86 87 this.fileName = file; 88 89 this.offset = offset; 90 91 this.length = length; 92 93 this.end = end; 94 95 this.httpClient = httpClient; 96 97 this.context = new BasicHttpContext(); 98 99 LOGGER.debug("偏移量=" + offset + ";字节数=" + length); 100 101 } 102 103 public void run() { 104 105 try { 106 107 HttpGet httpGet = new HttpGet(this.url); 108 httpGet.addHeader("Range", "bytes=" + this.offset + "-" 109 + (this.offset + this.length - 1)); 110 CloseableHttpResponse response = httpClient.execute(httpGet, 111 context); 112 ; 113 BufferedInputStream bis = new BufferedInputStream(response 114 .getEntity().getContent()); 115 116 byte[] buff = new byte[1024]; 117 118 int bytesRead; 119 120 File newFile = new File(fileName); 121 122 RandomAccessFile raf = new RandomAccessFile(newFile, "rw"); 123 124 while ((bytesRead = bis.read(buff, 0, buff.length)) != -1) { 125 raf.seek(this.offset); 126 raf.write(buff, 0, bytesRead); 127 this.offset = this.offset + bytesRead; 128 } 129 raf.close(); 130 bis.close(); 131 } catch (ClientProtocolException e) { 132 LOGGER.error("DownloadThread exception msg:{}",ExceptionUtils.getFullStackTrace(e)); 133 } catch (IOException e) { 134 LOGGER.error("DownloadThread exception msg:{}",ExceptionUtils.getFullStackTrace(e)); 135 } finally { 136 end.countDown(); 137 LOGGER.info(end.getCount() + " is go on!"); 138 System.out.println(end.getCount() + " is go on!"); 139 } 140 } 141 142}
application.xml
1<bean id="taskExecutor" 2 class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 3 <!-- 线程池活跃的线程数 --> 4 <property name="corePoolSize" value="5" /> 5 <!-- 线程池最大活跃的线程数 --> 6 <property name="maxPoolSize" value="10" /> 7 <!-- 队列的最大容量 --> 8 <property name="queueCapacity" value="600" /> 9 </bean> 10 <bean id="downLoadManager" 11 class="xx.DownLoadManagerTest"> 12 <property name="taskExecutor" ref="taskExecutor" /> 13 </bean>
测试运行,500M,我这网速得半个小时左右。要想下载更大的文件,只要jdk内存够大,就无限更改队列最大容量吧。
如果不同意见,欢迎各位大神指正。