- 浏览: 731422 次
- 性别:
- 来自: 嘉兴
文章分类
- 全部博客 (386)
- Struts1.1 (2)
- Database (18)
- Core Java (15)
- Log4j (4)
- SSH (0)
- Dao (1)
- Architecture Design (1)
- References (2)
- Eclipse&MyEclipse (10)
- Hibernate (7)
- Spring (8)
- JavaMail (1)
- Data Structure And Algorithm (48)
- Struts 2 (2)
- SSI (1)
- SSL (2)
- JSTL (1)
- EJB3 (2)
- NET (2)
- XML (2)
- Components (2)
- Ant (3)
- Multi Thread (1)
- Performance Monitoring (1)
- Web Server (17)
- Oracle (1)
- jQuery (8)
- Regular Expression (1)
- Weblogic (1)
- Exception (1)
- Security (2)
- File Manipulation (1)
- JavaScript (12)
- JVM (2)
- HTML&DIV&CSS (4)
- Android (10)
- Beyond GFW (0)
- Business (0)
- SVN (6)
- 虚拟主机 (1)
- Virtual Host (3)
- My mentality (5)
- OS (15)
- ISPMP (3)
- Magento (5)
- Jsoup&HttpClient (7)
- LINUX (9)
- Database Design (0)
- Power Designer (1)
- TaobaoOpenPlatform (2)
- C/C++ (3)
- Maven (11)
- Quartz (1)
- Load Balance (1)
- Zabbix (4)
- Product&Business (1)
- Pay Interface (1)
- Tomcat (2)
- Redis (1)
- 集群 (1)
- Session (1)
- 共享Session (1)
- Jedis (1)
- jenkins (1)
- 持续集成 (1)
- Web前端 (1)
最新评论
-
aqq331325797:
特意注册账号上来说一句。牛逼!
swagger2.2.2 与 spring cloud feign冲突 -
KitGavinx:
跨顶级域名怎么保持sessionid一致?
Tomcat7集群共享Session 基于redis进行统一管理 -
jaychang:
dujianqiao 写道HI ,能否给一个完整的demo 啊 ...
淘宝订单同步方案 - 丢单终结者 -
GGGGeek:
找了一会儿,感觉mybatis应该没有这种操作,直到发现博主的 ...
mybatis collection list string -
dujianqiao:
HI ,能否给一个完整的demo 啊 ?
淘宝订单同步方案 - 丢单终结者
在annegu写的多线程断点续传实践的基础上做了一些修改与重构,annegu写的多线程断点续传实践文章的地址:http://annegu.iteye.com/blog/427397
实现的主要功能有:1.任意个线程分段下载2.断点续传3.临时文件合并成所需文件,并删除临时文件
一、首先看一个常量类,用于定义下载状态,下载目录:
import java.io.File; /** * 下载相关信息常量类 * * @author jaychang * */ public class DownloadConstant { /** 下载默认路径 ,为当前使用系统的用户的Downloads的目录*/ public final static String DOWNLOAD_DIRECTORY = System.getProperty("user.home")+File.separator+"Downloads"; /** 下载已经完成 */ public final static String DOWNLOAD_HAS_FINISHED = "DOWNLOAD_HAS_FINISHED"; /** 下载出现错误 */ public final static String DOWNLOAD_ERROR = "DOWNLOAD_ERROR"; }
二、请求头设置
import java.net.URLConnection; /** * 请求头设置工具类 * * @author jaychang * */ public class RequestHeaderUtil { /** * 模拟发送HTTP请求 * * @param con * URLConnection */ public static void setHeader(URLConnection conn) { conn .setRequestProperty( "User-Agent", "Mozilla/5.0 (X11; U; Linux i686;en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3"); conn .setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3"); conn.setRequestProperty("Accept-Encoding", "aa"); conn.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); conn.setRequestProperty("Keep-Alive", "300"); conn.setRequestProperty("Connection", "keep-alive"); conn.setRequestProperty("If-Modified-Since", "Fri, 02 Jan 2009 17:00:05 GMT"); conn.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\""); conn.setRequestProperty("Cache-Control", "max-age=0"); } }
该方法有个URLConnection类型的参数,有些网站为了安全起见,会对请求的http连接进行过滤,因此为 了伪装这个http的连接请求,我们给httpHeader穿一件伪装服。下面的setHeader方法展示了一些非常常用的典型的httpHeader 的伪装方法。比较重要的 有:Uer-Agent模拟从Ubuntu的firefox浏览器发出的请求;Referer模拟浏览器请求的前一个触发页面,例如从skycn站点来下 载软件的话,Referer设置成skycn的首页域名就可以了;Range就是这个连接获取的流文件的起始区间。
三、线程下载管理类
import java.io.IOException; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import cn.com.servyou.mutithreaddown.contants.DownloadConstant; import cn.com.servyou.mutithreaddown.util.MergeFileUtil; import cn.com.servyou.mutithreaddown.util.RequestHeaderUtil; /** * 下载管理类 * * @author jaychang * */ public class DownloadManager { /**默认的线程数,当然可以改成使用配置文件进行设置*/ public final static int THREAD_TOTAL_NUM = 10; /** 文件总大小 */ private long contentLength; /** 起始索引数组,记录相应线程下载段的起始位置 */ private long[] startPoints = new long[THREAD_TOTAL_NUM]; /** 终止索引数组 ,记录相应线程下载段的终止位置*/ private long[] endPoints = new long[THREAD_TOTAL_NUM]; /** URL */ private String urlStr; /** 编码 */ @SuppressWarnings("unused") private final static String DEFAULT_ENCODING = "GBK"; public String getUrlStr() { return urlStr; } public void setUrlStr(String urlStr) { this.urlStr = urlStr; } public DownloadManager(String urlStr){ this.urlStr = urlStr; } public void download() throws IOException { URL url = new URL(urlStr); URLConnection conn = url.openConnection(); RequestHeaderUtil.setHeader(conn); // 下载文件的文件名 String fileAllName = urlStr.substring(urlStr.lastIndexOf("/") + 1); // 下载文件的大小 contentLength = conn.getContentLength(); // 计算每个线程需下载的大小 long contentLengthPerThread = contentLength / THREAD_TOTAL_NUM; System.out.println("Toltal bytes is " + contentLength); System.out.println("Every thread need read bytes is " + contentLengthPerThread); ExecutorService exec = Executors.newCachedThreadPool(); CountDownLatch latch = new CountDownLatch(THREAD_TOTAL_NUM); for (int i = 0; i < THREAD_TOTAL_NUM; i++) { startPoints[i] = contentLengthPerThread * i; endPoints[i] = (i == THREAD_TOTAL_NUM - 1) ? contentLength - 1 : contentLengthPerThread * (i + 1) - 1; DownloadThread downloadThread = new DownloadThread(); downloadThread.setContentLength(contentLength); // 设置下载片段起始位置 downloadThread.setStartPoint(startPoints[i]); // 设置下载片段结束位置 downloadThread.setEndPoint(endPoints[i]); // 设置下载文件的全名 downloadThread.setFileAllName(fileAllName); downloadThread.setUrlStr(urlStr); downloadThread.setThreadIndex(i + 1); downloadThread.setLatch(latch); // 线程开始执行 exec.execute(downloadThread); } try { // 等待CountdownLatch信号为0,表示所有子线程都执行结束 latch.await(100000, TimeUnit.MILLISECONDS); exec.shutdown(); // 把分段下载下来的临时文件中的内容写入目标文件中。 MergeFileUtil.merge(DownloadConstant.DOWNLOAD_DIRECTORY + "/" + fileAllName, fileAllName); } catch (InterruptedException e) { e.printStackTrace(); } } }
下面对以上代码做一个解释:
首先说明一下该类的几个常量,及属性,THREAD_TOTAL_NUM也不用多说了,contentLength为下载文件的总的字节数,startPoint[THREAD_TOTAL_NUM]存着每个线程所负责要下载的片段的起始位置,endPoint[THREAD_TOTAL_NUM]就不用说了,urlStr必须是http协议的url,且结尾是文件名+文件后缀名(fileName.type),编码暂时还未用到,先放着。
其次,说下CountdownLatch,CountdownLatch就是一个计数器,就像一个拦截的栅栏,用await()方法来把栅栏关上,线程就跑不下去了,只有等计数器减为0的时候,栅栏才会自动打开,被暂停的线程才会继续运行。CountdownLatch的应用场景可以有很多,分段下载就是一个很好的例子。
四、下载线程类
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.CountDownLatch; import cn.com.servyou.mutithreaddown.contants.DownloadConstant; import cn.com.servyou.mutithreaddown.util.RequestHeaderUtil; /** * 下载线程类 * * @author jaychang * */ public class DownloadThread extends Thread { /** 该线程所需下载文件片段的开始位置 */ private int threadIndex; private long startPoint; /** 该线程所需下载的文件片段的结束位置 */ private long endPoint; /** 下载文件的总大小 */ private long contentLength; /** 文件全名 */ private String fileAllName; /** 文件的url地址 */ private String urlStr; /** 缓冲大小 */ private final static int BUFFER_READ_SIZE = 8096; /** 临时文件后缀名 */ private final static String FILE_TYPE = "tmp"; /** 初始化下载状态 */ private String status = DownloadConstant.DOWNLOAD_ERROR; /** CountDownLatch 用作与主线程同步,等到所有线程都执行完毕,则主线程开始对临时文件进行拼装 */ private CountDownLatch latch; public DownloadThread() { super(); } /** * 构造器 * * @param threadIndex * 线程索引 * @param startPoint * 下载开始位置 * @param endPoint * 下载结束位置 * @param contentLength * 文件总的大小 * @param fileAllName * 文件全名 * @param urlStr * url字符串 * @param latch * CountDownLatch */ public DownloadThread(int threadIndex, long startPoint, long endPoint, long contentLength, String fileAllName, String urlStr, CountDownLatch latch) { super(); this.threadIndex = threadIndex; this.startPoint = startPoint; this.endPoint = endPoint; this.contentLength = contentLength; this.fileAllName = fileAllName; this.urlStr = urlStr; this.latch = latch; start(); } /** * 下载文件部分内容,并存于临时文件中 */ public void run() { int indexOfPoint = fileAllName.lastIndexOf("."); String fileName = fileAllName.substring(0, indexOfPoint); String tempFileName = fileName + "_" + threadIndex + "." + FILE_TYPE; File downloadDir = new File(DownloadConstant.DOWNLOAD_DIRECTORY + "/" + fileAllName); if (!downloadDir.exists()) { downloadDir.mkdirs(); } File tempFile = new File(downloadDir, tempFileName); boolean isExist = tempFile.exists(); if (isExist) { long localContentLength = tempFile.length(); processDownload(tempFileName, localContentLength); } else { processDownload(tempFileName, 0); } } /** * 处理该线程的下载任务 * * @param tempFileName * 临时文件名 * @param localContentLength * 临时文件大小 */ public void processDownload(String tempFileName, long localContentLength) { HttpURLConnection conn = null; BufferedInputStream in = null; BufferedOutputStream out = null; // 该线程需下载的字节片段未下载完 if (localContentLength < endPoint - startPoint + 1) { try { conn = (HttpURLConnection) (new URL(urlStr)).openConnection(); conn.setAllowUserInteraction(true); // 设置连接超时时间为10000ms conn.setConnectTimeout(10000); // 设置读取数据超时时间为10000ms conn.setReadTimeout(100000); RequestHeaderUtil.setHeader(conn); // 设置请求头读取字节的范围,即断点起始位置 long startPos = startPoint + localContentLength; conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPoint); System.out.println("Thread" + threadIndex + ": " + " startPos=" + startPos + " endPos=" + endPoint + " NeedReadBytes=" + (endPoint - startPos + 1)); int responseCode = conn.getResponseCode(); if (HttpURLConnection.HTTP_OK == responseCode) { System.out.println("HTTP_OK"); } else if (HttpURLConnection.HTTP_PARTIAL == responseCode) { System.out.println("HTTP_PARTIAL"); } else if (HttpURLConnection.HTTP_CLIENT_TIMEOUT == responseCode) { System.out.println("HTTP_CLIENT_TIMEOUT"); } File directory = new File(DownloadConstant.DOWNLOAD_DIRECTORY + "/" + fileAllName); in = new BufferedInputStream(conn.getInputStream()); out = new BufferedOutputStream(new FileOutputStream(new File( directory, tempFileName), true)); long count = 0; byte[] b = new byte[BUFFER_READ_SIZE]; int len = -1; // 需要读取的字节数 long needReadBytes = endPoint - startPos + 1; while ((len = in.read(b)) != -1) { count += len; if (count > needReadBytes) { System.out.println("Current read " + len + " Thread " + threadIndex + " has readed " + count + " bytes!"); System.out.println("Thread " + threadIndex + " finished!"); break; } out.write(b, 0, len); } // 设置最终该线程的下载状态 this.status = count >= needReadBytes ? DownloadConstant.DOWNLOAD_HAS_FINISHED : DownloadConstant.DOWNLOAD_ERROR; // 线程池计数减1,表示线程池中该线程的任务已结束 latch.countDown(); } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) try { in.close(); } catch (IOException e1) { e1.printStackTrace(); } if (out != null) try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } else if (localContentLength >= endPoint - startPoint + 1) { System.out.println("Thread " + (threadIndex + 1) + "," + "needReadBytes = " + (endPoint - startPoint)); this.status = DownloadConstant.DOWNLOAD_HAS_FINISHED; latch.countDown(); } } public long getStartPoint() { return startPoint; } public void setStartPoint(long startPoint) { this.startPoint = startPoint; } public long getEndPoint() { return endPoint; } public void setEndPoint(long endPoint) { this.endPoint = endPoint; } public long getContentLength() { return contentLength; } public void setContentLength(long contentLength) { this.contentLength = contentLength; } public String getFileAllName() { return fileAllName; } public void setFileAllName(String fileAllName) { this.fileAllName = fileAllName; } public int getThreadIndex() { return threadIndex; } public void setThreadIndex(int threadIndex) { this.threadIndex = threadIndex; } public String getUrlStr() { return urlStr; } public void setUrlStr(String urlStr) { this.urlStr = urlStr; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public CountDownLatch getLatch() { return latch; } public void setLatch(CountDownLatch latch) { this.latch = latch; } }
该类的属性中有threadIndex,用于定义临时文件,临时文件定义为"文件名_threadIndex.后缀名"
五、临时文件合并工具类。
现在每个分段的下载线程都顺利结束了,也都创建了相应的临时文件,接下来在主线程中会对临时文件进行合并,并写入目标文件,最后删除临时文件。
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; /** * 临时文件合并工具类 * * @author jaychang * */ public class MergeFileUtil { /** 下划线分隔符 */ public final static String UNDER_LINE = "_"; /** 点分隔符 */ public final static String POINT = "."; /** * 合并临时文件 * * @param filePath * 临时文件路径 * @param fileAllName * @throws IOException */ public static void merge(String filePath, String fileAllName) throws IOException { File dir = new File(filePath); BufferedOutputStream out = null; BufferedInputStream in = null; File[] fileList = dir.listFiles(); // 对临时文件进行排序,按照顺序写到输出流中,排序规则为按照文件名的threadIndex(fileAllName_'threadIndex'.type)排序 Arrays.sort(fileList, new Comparator<File>() { public int compare(File fileOne, File fileAnother) { String fileNameOne = fileOne.getName(); String fileNameAnother = fileAnother.getName(); int lastIndexOfUnderLineOne = fileNameOne .lastIndexOf(UNDER_LINE); int lastIndexOfPointOne = fileNameOne.lastIndexOf(POINT); int lastIndexOfUnderLineAnother = fileNameAnother .lastIndexOf(UNDER_LINE); int lastIndexOfPointAnother = fileNameAnother .lastIndexOf(POINT); int one = Integer.parseInt(fileNameOne.substring( lastIndexOfUnderLineOne + 1, lastIndexOfPointOne)); int another = Integer.parseInt(fileNameAnother.substring( lastIndexOfUnderLineAnother+1, lastIndexOfPointAnother)); return one - another; } }); // 由临时文件拼装成的最终文件 File destFile = new File(dir, fileAllName); if (!destFile.exists()) { destFile.createNewFile(); } out = new BufferedOutputStream(new FileOutputStream(destFile, true)); for (File file : fileList) { // 过滤非临时文件 if (file.getName().indexOf("tmp") < 0) continue; // 读取临时文件,按文件编号(处理该文件的线程索引)顺序写入最终生成的文件 in = new BufferedInputStream(new FileInputStream(file)); byte[] b = new byte[8196]; int len = -1; while ((len = in.read(b)) != -1) { out.write(b, 0, len); } if (in != null) { in.close(); } // 删除临时文件 file.delete(); } if (out != null) { out.close(); } } }
遍历文件下载的临时文件,并对临时文件数组进行排序,排序按照前面讲到的threadIndex,因为之前定义临时文件名称为"文件名_threadIndex.后缀名"。
- mutithreaddown.rar (34.1 KB)
- 下载次数: 34
- mutithreaddown_V2.rar (17.7 KB)
- 下载次数: 36
评论
else if (localContentLength >= endPoint - startPoint + 1) {
System.out.println("Thread " + (threadIndex + 1) + ","
+ "needReadBytes = " + (endPoint - startPoint));
this.status = DownloadConstant.DOWNLOAD_HAS_FINISHED;
latch.countDown();
}
可解决
可以,但是现在是强行结束程序,正是现在我觉得头痛的问题,暂时没有好的办法让其在下载过程中停止。再次执行程序时,如果还是原先的url,可以断点续传
this.status = DownloadConstant.DOWNLOAD_HAS_FINISHED;
latch.countDown();
是强行结束吗 不过我多试了几次后,文件最后好像不合并了
能否添加个个界面?
你说的对,暂时是只能强行结束,有点恶心哈,多谢你的建议哈,我在检查下
是强行结束吗 不过我多试了几次后,文件最后好像不合并了
能否添加个个界面?
相关推荐
李白高力士脱靴李白贺知章告别课本剧.pptx
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
C语言项目之超级万年历系统源码,可以做课程设计参考 文章参考:https://www.qqmu.com/4373.html
Jupyter-Notebook
51单片机加减乘除计算器系统设计(proteus8.17,keil5),复制粘贴就可以运行
《中国房地产统计年鉴》面板数据资源-精心整理.zip
Jupyter-Notebook
Jupyter-Notebook
毕业论文答辩ppt,答辩ppt模板,共18套
Jupyter-Notebook
《中国城市统计年鉴》面板数据集(2004-2020年,最新).zip
Python基础 本节课知识点: • set的定义 • Set的解析 • set的操作 • set的函数
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
兵制与官制研究资料最新版.zip
Jupyter-Notebook
七普人口数据+微观数据+可视化+GIS矢量资源-精心整理.zip
Support package for Hovl Studio assets.unitypackage
土壤数据库最新集.zip
Jupyter-Notebook
1991-2020年中国能源统计年鉴-能源消费量(分省)面板数据-已更至最新.zip