多线程断点续传下载
2016-05-27 18:27
218 查看
骚年,你是否为你写的下载模块的下载速度相比UC,豌豆荚,应用宝等慢太多而烦恼。本人刚工作时写的下载模块,期间优化来优化去,网上的相关多线程断点续传帖子也看了好多,但总是赶不上UC的下载速度!!! 最近,项目闲下来了,决定再次优化下载模块。finally!耶,媲美了UC的下载速度,杠杠的!
先讲大体实现思路,再讲其中各种导致下载速度上不去的坑。
原理:下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度。
1. 请求下载链接地址,获取getContentLength,也就是文件总大小。
2. 根据文件总大小,决定分几个子线程块去下载。
3. 根据分好的块开启子线程开始下载。
这一块核心主要是两个
一: 设置http请求的文件长度,http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
二:使用RandomAccessFile来实现多线程写入同一文件。
=======================================分割线=============================================
大体实现思路就是如上文所述,是不是感觉没几步,很简单? 0.0
但其实真正做起来还有很多细节,很多坑在。接下来,作为一个老司机,就谈谈我踩过的一些坑。
1.第一个问题就是分块,到底怎么分,分几个块合适。
博主最先开始从单线程下载改成多线程下载时,分的子线程都是2个...或者3个.... 改完后对比,差别真的不大....很尴尬,现在想想,当初
太小气了。优化后的分块逻辑文章上半部分已贴,500K一个子线程,16子线程上限。
2.第二个问题是RandomAccessFile的创建。
网上帖子大多数是这么创建的:new RandomAccessFile(file, "rws");
rws模式下文件的写入是同步的,不是异步的,会大大降低多线程写入文件的速度哦。
所以,正确的姿势是这样子的: new RandomAccessFile(file, "rw");
3.第三个问题是各子线程下载进度的保存问题。
有些同学为了实现断点续传这个功能,跟博主年轻的时候一样,想当然的在写入文件时更新数据库。navie
频繁读写数据库同样也是降低下载速度的因素。
优雅的做法是在异常时才去更新保存进度。或者设置一个阀值。
先讲大体实现思路,再讲其中各种导致下载速度上不去的坑。
原理:下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度。
1. 请求下载链接地址,获取getContentLength,也就是文件总大小。
public boolean initDownLoadFileSize() { try { HttpURLConnection conn = (HttpURLConnection) new URL(downLoadUrl) .openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.connect(); int responseCode = conn.getResponseCode(); if (responseCode == 200) { fileSize = conn.getContentLength(); return true; } } catch (IOException e) { } return false; }
2. 根据文件总大小,决定分几个子线程块去下载。
private void convertChunks(){ int MaximumUserCHUNKS = chunks/2; chunks = 1; for (int f=1 ; f <=MaximumUserCHUNKS ; f++) if (fileSize > MegaByte*f) chunks = f*2; data = FileDB.getInstance().getFileDownChunkLog(this.package_name); if (data.size() == chunks) { for (int i = 0; i < chunks; i++) { alreadyDownSize += data.get(i + 1); } } else { data.clear(); for (int i = 0; i < chunks; i++) { data.put(i + 1, 0);// 初始化每条线程已经下载的数据长度为0 } FileDB.getInstance().deleteFileDownChunksLog(this.package_name); FileDB.getInstance().saveFileDownChunkLog(this.package_name, data); } workList = new DownloadThread[chunks]; }这一步骤核心:使用数据库维护各线程的下载进度,从而实现断点续传的功能。下载之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库
3. 根据分好的块开启子线程开始下载。
void startDownloadChunk(){ InputStream inStream = null; RandomAccessFile threadfile = null; try { HttpURLConnection http = (HttpURLConnection) new URL(downUrl) .openConnection(); http.setConnectTimeout( 10 * 1000 ); http.setReadTimeout( 10 * 1000 ); int startPos = block * (threadId - 1) + downLength; int endPos; if (threadId == downloader.getThreadNum()) { endPos = downloader.getFileSize(); } else { endPos = block * threadId - 1; } http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos); inStream = http.getInputStream(); byte[] buffer = new byte[1024]; int offset; threadfile = new RandomAccessFile(this.saveFile, "rw"); threadfile.seek(startPos); while ( (offset = inStream.read(buffer)) > 0 ) { threadfile.write(buffer, 0, offset); downLength += offset; downloader.append(offset); if (isPause) { downloader.Pause(); return; } } downloader.checkDownloadFinish(this); } catch (Exception e) { LogUtil.i(e.toString()); if (tryTime < MAX_RETRY_TIMES) { tryTime++; startDownloadChunk(); }else { downloader.Pause(); MyDownloadManager.getInstance().downFail(downloader, "下载失败,请重试", true); } } finally { downloader.update(threadId, downLength); try { if (threadfile != null) threadfile.close(); if (inStream != null) inStream.close(); } catch (IOException e) { } } }
这一块核心主要是两个
一: 设置http请求的文件长度,http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
二:使用RandomAccessFile来实现多线程写入同一文件。
=======================================分割线=============================================
大体实现思路就是如上文所述,是不是感觉没几步,很简单? 0.0
但其实真正做起来还有很多细节,很多坑在。接下来,作为一个老司机,就谈谈我踩过的一些坑。
1.第一个问题就是分块,到底怎么分,分几个块合适。
博主最先开始从单线程下载改成多线程下载时,分的子线程都是2个...或者3个.... 改完后对比,差别真的不大....很尴尬,现在想想,当初
太小气了。优化后的分块逻辑文章上半部分已贴,500K一个子线程,16子线程上限。
2.第二个问题是RandomAccessFile的创建。
网上帖子大多数是这么创建的:new RandomAccessFile(file, "rws");
rws模式下文件的写入是同步的,不是异步的,会大大降低多线程写入文件的速度哦。
所以,正确的姿势是这样子的: new RandomAccessFile(file, "rw");
3.第三个问题是各子线程下载进度的保存问题。
有些同学为了实现断点续传这个功能,跟博主年轻的时候一样,想当然的在写入文件时更新数据库。navie
频繁读写数据库同样也是降低下载速度的因素。
优雅的做法是在异常时才去更新保存进度。或者设置一个阀值。
相关文章推荐
- Hibernate的Restrictions用法
- EasyDarwin开源流媒体社区视频教程
- 导出csv文件数字会自动变科学计数法的解决方法
- HashMap 和 HashTable 的区别
- Solr分面搜索(Faceting)
- 怎么去掉Xcode工程中的某种类型的警告
- RecyclerView嵌套RecyclerView
- Java的强引用、软引用、弱引用、虚引用
- RecyclerView嵌套RecyclerView
- 根据传入的地址获取网络文件大小
- CSAPP3e - x86-64 assembly code analysis - Bomb Lab: phase 1
- avfoundation 合成一组图片到视频
- 三,基本语法形式
- 第二阶段团队项目冲刺站立会议(四)
- 嵌入式linux开发环境搭建(三)——TFTP服务器的搭建
- C# 微软源码
- androidstudio中如何将单独的model形式的library转换成一个单独的工程!
- Oracle操作数据库oracleHelper
- [RxJS] Transformation operator: buffer, bufferCount, bufferTime
- unity android 退出异常 google play game services