--> 默认最多50个线程 同一文件下载失败延迟超过30秒就结束下载

--> 下载5分钟超时时间,假设5分钟内未下载完就结束下载

--> 依赖 commons-httpclient 与 commons-io 包

package com.leunpha;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import java.io.*;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Observable;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.ZipFile; /**
* User: zhoujingjie
* Date: 14-4-18
* Time: 下午12:52
*/
public class Downloader extends Observable {
protected String url, savePath; //下载地址与保存路径
protected FileChannel channel; //保存文件的通道
protected long size, perSize; //文件大小与每一个小文件的大小
protected volatile long downloaded; // 已下载的
protected int connectCount; //连接数
protected Connection[] connections; //连接对象
protected boolean isSupportRange; //是否支持断点下载
protected long timeout; //超时
protected boolean exists; //是否存在
private RandomAccessFile randomAccessFile;
protected volatile boolean stop; //停止
private static volatile boolean exception; //是否异常
private AtomicLong prevDownloaded = new AtomicLong(0); //上一次的下载结果
private static Log log = LogFactory.getLog(Downloader.class);
private AtomicInteger loseNum = new AtomicInteger(0);
private int maxThread; public Downloader(String url, String savePath) throws IOException {
//超时一小时
this(url, savePath, 1000 * 60*5,50);
} public Downloader(String url, String savePath, long timeout,int maxThread) throws FileNotFoundException {
this.timeout = timeout;
this.url = url;
this.maxThread = maxThread;
File file = new File(savePath);
if (!file.exists()) file.mkdirs();
this.savePath= file.getAbsolutePath() + "/" + url.substring(url.lastIndexOf("/"));
exists = new File(this.savePath).exists();
if(!exists){
randomAccessFile= new RandomAccessFile(this.savePath+".temp", "rw");
channel =randomAccessFile.getChannel();
}
} public GetMethod method(long start, long end) throws IOException {
GetMethod method = new GetMethod(Downloader.this.url);
method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36");
if (end > 0) {
method.setRequestHeader("Range", "bytes=" + start + "-" + (end - 1));
} else {
method.setRequestHeader("Range", "bytes=" + start + "-");
}
HttpClientParams clientParams = new HttpClientParams();
//5秒超时
clientParams.setConnectionManagerTimeout(5000);
HttpClient client = new HttpClient(clientParams);
client.executeMethod(method);
int statusCode = method.getStatusCode();
if (statusCode >= 200 && statusCode < 300) {
isSupportRange = (statusCode == 206) ? true : false;
}
return method;
} public void init() throws IOException {
size = method(0, -1).getResponseContentLength();
if (isSupportRange) {
if (size < 4 * 1024 * 1024) { //假设小于4M
connectCount = 1;
} else if (size < 10 * 1024 * 1024) { //假设文件小于10M 则两个连接
connectCount = 2;
} else if (size < 30 * 1024 * 1024) { //假设文件小于80M 则使用6个连接
connectCount = 3;
} else if (size < 60 * 1024 * 1024) { //假设小于60M 则使用10个连接
connectCount = 4;
} else {
//否则为10个连接
connectCount = 5;
}
} else {
connectCount = 1;
}
log.debug(String.format("%s size:%s connectCount:%s", this.url, this.size, this.connectCount));
perSize = size / connectCount;
connections = new Connection[connectCount];
long offset = 0;
for (int i = 0; i < connectCount - 1; i++) {
connections[i] = new Connection(offset, offset + perSize);
offset += perSize;
}
connections[connectCount - 1] = new Connection(offset, size);
} /**
* 强制释放内存映射
*
* @param mappedByteBuffer
*/
static void unmapFileChannel(final MappedByteBuffer mappedByteBuffer) {
try {
if (mappedByteBuffer == null) {
return;
}
mappedByteBuffer.force();
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
getCleanerMethod.setAccessible(true);
sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer, new Object[0]);
cleaner.clean();
} catch (Exception e) {
//LOG.error("unmapFileChannel." + e.getMessage());
}
return null;
}
});
} catch (Exception e) {
log.debug("异常->exception=true");
exception = true;
log.error(e);
}
} private void timer() {
Timer timer = new Timer();
//延迟3秒,3秒执行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
log.debug(String.format("已下载-->%s -> %s",(((double) downloaded) / size * 100) + "%", url));
//假设上一次的下载大小与当前的一样就退出
if(prevDownloaded.get() ==downloaded && downloaded<size){
if(loseNum.getAndIncrement()>=10){
log.debug(String.format("上次下载%s与当前下载%s一致,exception->true url:%s ",prevDownloaded.get(),downloaded,url));
exception = true;
}
}
//假设下载完毕或者异常就退出
if(downloaded>=size || exception){
stop = true;
cancel();
}
//设置上次下载的大小等于如今的大小
prevDownloaded.set(downloaded);
}
},3000,3000);
} public void start() throws IOException {
if (exists) {
log.info("文件已存在." + this.url);
Thread.currentThread().interrupt();
return;
}
while (Thread.activeCount()>maxThread){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
init();
timer();
CountDownLatch countDownLatch = new CountDownLatch(connections.length);
log.debug("開始下载:" + url);
for (int i = 0; i < connections.length; i++) {
new DownloadPart(countDownLatch, i).start();
}
end(countDownLatch);
} private boolean rename(File tempFile){
File file = new File(this.savePath);
boolean isRename=tempFile.renameTo(file);
if(!isRename){
try {
IOUtils.copy(new FileInputStream(tempFile),new FileOutputStream(file));
} catch (IOException e) {
log.error(e);
}
}
return true;
} public void end(CountDownLatch countDownLatch){
try {
//超过指定时间就直接结束
countDownLatch.await(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
exception = true;
log.error(e);
log.info("下载失败:"+this.url);
} finally {
try {
channel.force(true);
channel.close();
randomAccessFile.close();
} catch (IOException e) {
log.error(e);
}
File temp = new File(this.savePath+".temp");
log.debug(String.format("%s %s", exception, this.url));
//假设有异常则删除已下载的暂时文件
if(exception){
if(!temp.delete()){
if(temp!=null)temp.delete();
}
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
rename(temp);
setChanged();
notifyObservers(this.url);
log.info("下载成功:"+this.url);
}
}
} private class Connection {
long start, end; public Connection(long start, long end) {
this.start = start;
this.end = end;
} public InputStream getInputStream() throws IOException {
return method(start, end).getResponseBodyAsStream();
}
} private class DownloadPart implements Runnable {
CountDownLatch countDownLatch;
int i; public DownloadPart(CountDownLatch countDownLatch, int i) {
this.countDownLatch = countDownLatch;
this.i = i;
}
public void start() {
new Thread(this).start();
}
@Override
public void run() {
MappedByteBuffer buffer = null;
InputStream is = null;
try {
is = connections[i].getInputStream();
buffer = channel.map(FileChannel.MapMode.READ_WRITE, connections[i].start, connections[i].end - connections[i].start);
byte[] bytes = new byte[4 * 1024];
int len;
while ((len = is.read(bytes)) != -1 && !exception && !stop) {
buffer.put(bytes, 0, len);
downloaded+= len;
}
log.debug(String.format("file block had downloaded.%s %s",i,url));
} catch (IOException e) {
log.error(e);
} finally {
unmapFileChannel(buffer);
if(buffer != null)buffer.clear();
if (is != null) try {
is.close();
} catch (IOException e) {
}
countDownLatch.countDown();
}
}
} }

Java Nio 多线程网络下载的更多相关文章

  1. java socket 多线程网络传输多个文件

    http://blog.csdn.net/njchenyi/article/details/9072845 java socket 多线程网络传输多个文件 2013-06-10 21:26 3596人 ...

  2. Java之多线程断点下载的实现

    RandomAccessFile类: 此类的实例支持对随机訪问文件的读取和写入.随机訪问文件的行为相似存储在文件系统中的一个大型 byte 数组. 存在指向该隐含数组.光标或索引,称为文件指针.输入操 ...

  3. Java NIO之网络编程

    最近在研究Java NIO和netty,曾经一度感觉很吃力,根本原因还是对操作系统.TCP/IP.socket编程的理解不到位. 不禁感叹,还是当初逃的课太多. 假如上天给我一次机会,能够再回到意气风 ...

  4. Java实现多线程断点下载(下载过程中可以暂停)

    线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开启好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配. ...

  5. Java NIO网络编程demo

    使用Java NIO进行网络编程,看下服务端的例子 import java.io.IOException; import java.net.InetAddress; import java.net.I ...

  6. Java NIO 操作总结

    问题: 1.Java NIO 出现大量CLOSE_WAIT或TIME_WAIT的端口无法释放 CLOSE_WAIT: 参考:http://my.oschina.net/geecoodeer/blog/ ...

  7. Java实现多线程下载,支持断点续传

    完整代码:https://github.com/iyuanyb/Downloader 多线程下载及断点续传的实现是使用 HTTP/1.1 引入的 Range 请求参数,可以访问Web资源的指定区间的内 ...

  8. Android实现网络多线程断点续传下载(转)

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

  9. Android实现网络多线程断点续传下载

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

随机推荐

  1. 264分析两大利器:264VISA和Elecard StreamEye Tools

    学了264有将近3个月有余,好多时候都在学习老毕的书和反复看JM86的代码,最近才找到264分析两大利器:264VISA和Elecard StreamEye Tools.不由得感叹,恨不逢同时. 简单 ...

  2. 转《深入理解Java虚拟机》学习笔记之最后总结

    编译器 Java是编译型语言,按照编译的时期不同,编译器可分为: 前端编译器:其实叫编译器的前端更合适些,它把*.java文件转变成*.class文件,如Sun的Javac.Eclipse JDT中的 ...

  3. Hibernate管理Session和批量操作

    Hibernate管理Session Hibernate自身提供了三种管理Session对象的方法 Session对象的生命周期与本地线程绑定 Session对象的生命周期与JTA事务绑定 Hiber ...

  4. ASP.NET MVC+Bootstrap个人博客之修复UEditor编辑时Bug(四)

    我的个人博客站在使用百度富文本编辑器UEditor修改文章时,遇到了一些问题,(不知是bug,还是我没有配置好).但总算找到了解决方法,在此记录下来. 小站首页文章列表显示为(显示去除HTML标签后的 ...

  5. [转] ArcEngine 产生专题图

    小生原文 ArcEngine 产生专题图 ArcEngine提供多个着色对象用于产生专题图,可以使用标准着色方案,也可以自定义着色方案,ArcEngine提供8中标准着色方案. 一.SimpleRen ...

  6. C语言内存地址基础

    来源:http://blog.jobbole.com/44845/ 从计算机内存的角度思考C语言中的一切东东,是挺有帮助的.我们可以把计算机内存想象成一个字节数组,内存中每一个地址表示 1 字节.比方 ...

  7. HDU 5278 PowMod 数论公式推导

    题意:中文题自己看吧 分析:这题分两步 第一步:利用已知公式求出k: 第二步:求出k然后使用欧拉降幂公式即可,欧拉降幂公式不需要互质(第二步就是BZOJ3884原题了) 求k的话就需要构造了(引入官方 ...

  8. BLOCK 死循环

    __weak typeof(self) weakSelf = self; myObj.myBlock =  ^{     __strong typeof(self) strongSelf = weak ...

  9. iOS 怎么样给自己的app打分呢?

    NSString *str =  [NSString stringWithFormat:@"itms-apps://itunes.apple.com/app/id%@",@&quo ...

  10. RTNETLINK answers: File exists错误

    解决ssh连接虚拟机出错,RTNETLINK answers: File exists 解决步骤如下: 使用ssh连接虚拟机的时候,发现目标主机无法连接,登录虚拟机,查看ssh监听是否开启: 发现监听 ...