--> 默认最多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. DataGuard相同SID物理Standby搭建

    Oracle Data Guard 是针对企业数据库的最有效和最全面的数据可用性.数据保护和灾难恢复解决方案.它提供管理.监视和自动化软件基础架构来创建和维护一个或多个同步备用数据库,从而保护数据不受 ...

  2. 四、oracle基本sql语句和函数详解

    一.oracle常用数据类型 一.  数据定义语言(ddl) 数据定义语言ddl(data definition language)用于改变数据库结构,包括创建.更改和删除数据库对象. 用于操纵表结构 ...

  3. VelocityTracker简单用法

    VelocityTracker顾名思义即速度跟踪,在android中主要应用于touch event, VelocityTracker通过跟踪一连串事件实时计算出 当前的速度,这样的用法在androi ...

  4. Entity Framework 5.0

    今天 VS2012  .net Framework 4.5   Entity Framework 5.0  三者共同发布了. ( EF5 Released ) 在介绍新特性之前,先与大家回顾一下EF版 ...

  5. codeforces 675C Money Transfers map

    上面是官方题解,写的很好,然后就A了,就是找到前缀和相等的最多区间,这样就可以减去更多的1 然后肯定很多人肯定很奇怪为什么从1开始数,其实从2开始也一样,因为是个环,从哪里开始记录前缀和都一样 我们的 ...

  6. 《Python 学习手册4th》 第十章 Python语句简介

    ''' 时间: 9月5日 - 9月30日 要求: 1. 书本内容总结归纳,整理在博客园笔记上传 2. 完成所有课后习题 注:“#” 后加的是备注内容 (每天看42页内容,可以保证月底看完此书) “重点 ...

  7. linux常用命令之--目录与文件的操作命令

    1.linux的目录与文件的增.删.改.复制 pwd:用于显示当前所在的目录 ls:用于显示指定目录下的内容 其命令格式如下: ls [-option] [file] 常用参数: -l:显示文件和目录 ...

  8. memcpy、memmove、memset及strcpy函数实现和理解

    memcpy.memmove.memset及strcpy函数实现和理解 关于memcpy memcpy是C和C++ 中的内存拷贝函数,在C中所需的头文件是#include<string.h> ...

  9. Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF

    在实时渲染中Physically-Based Rendering(PBR)中文为基于物理的渲染它能为渲染的物体带来更真实的效果,而且能量守恒 稍微解释一下字母的意思,为对后文的理解有帮助,从右到左L为 ...

  10. 使用C语言实现二维,三维绘图算法(1)-透视投影

    使用C语言实现二维,三维绘图算法(1)-透视投影 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其实想想 ...