网络蚂蚁、FlashGet、迅雷等支持HTTP协议的下载软件无一例外地使用了多线程下载技术。比起单线程下载,多线程下载在同一时间段内发出多个下载请求,每个下载请求负责下载一段内容,充分地利用了网络带宽。
当然多线程下载并非线程数越多越好。试想,一个极端 的情况:一个尺寸为1 024个字节的远程文件,动用1 024个线程来下载,每个线程平均只下载一个字节,创建线程的代价和对自身网络出口造成的堵塞远远大于分工下载带来的好处。因此,多线程下载存在一个权衡 的问题。一般来说,需要事先根据待下载的远程文件的尺寸来决定启用多少个线程,如果文件很小,则意味着使用单线程下载即可。而且,下载软件允许创建的线程 数一般是设置上限的,例如即使下载一个超大文件,也不能开启过多的线程,毕竟创建下载线程是需要耗费客户端资源的,并且线程之间存在着竞争网络带宽的关 系。总之,下载线程数往往是在待下载的远程文件尺寸、每个线程分担的字节数任务、线程数三者之间权衡的结果。
实战多线程下载,有几个技术难题有待攻克:
— 如何获取远程文件的尺寸,这关系到开启多少个下载线程。本节例程采用比较简单的线程数决策策略:固定每个线程分担的字节数任务,根据远程文件尺寸来决定需 要开启的下载线程,也就是不考虑下载线程数过多带来的负面影响。因此,获取远程文件的尺寸就成为了很关键的一个步骤。
— 如何实现分工下载,即每个线程只下载远程文件的一段。这是多线程HTTP下载的核心技术。
— 如何存储、组织各个线程下载得到的文件碎片,最后将其拼成一个完整的文件。
package com.yt.manager.net.download;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Description:
* @ClassName: DownFile
* @Project: base-info
* @Author: zxf
* @Date: 2011-7-13
*/
public class DownFile {
/**
* 下载
*
* @throws Exception
*
*/
public void down(String path, int threadnum) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10 * 1000);
conn.setRequestMethod("GET");
// 获得网络文件的长度
int length = conn.getContentLength();
// 每个线程负责下载的文件大小
int block = (length % threadnum) == 0 ? length / threadnum : length
/ threadnum + 1;
//从http相应消息获取的状态码,200:OK;401:Unauthorized
if (conn.getResponseCode() == 200) {
for (int i = 0; i < threadnum; i++) {
// 开启线程下载
new DownThread(i, new File(realPath(),getFileName(path)), block, url).start();
}
}
}
/**
* 文件的下载目录
* @return
*/
public String realPath(){
String realPath = "h:/download";
File file = new File(realPath);
if (!file.exists()) {
file.mkdirs();
}
return realPath;
}
/**
* 获取文件名
* @param path
* @return
*/
public String getFileName(String path) {
return path.substring(path.lastIndexOf("/") + 1);
}
public static void main(String[] args) {
DownFile test = new DownFile();
//String path = "http://www.java.net/download/jdk6/6u10/promoted/b32/binaries/jdk-6u10-rc2-bin-b32-windows-i586-p-12_sep_2008.exe";
String path ="http://www.baidu.com/img/baidu_sylogo1.gif";
int threadnum = 3;
try {
test.down(path, threadnum);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.yt.manager.net.download;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Description:
* @ClassName: DownThread
* @Project: base-info
* @Author: zxf
* @Date: 2011-7-13
*/
public class DownThread extends Thread {
private int id; // 线程id
private File file;// 目标文件
private int block;// 每个线程下载文件的大小
private URL url;
public DownThread() {
}
public DownThread(int id, File file, int block, URL url) {
this.id = id;
this.file = file;
this.block = block;
this.url = url;
}
@Override
public void run() {
int start = (id * block);// 当前线程开始下载处
int end = (id + 1) * block - 1;// 当前线程结束下载处
// 建立随机操作文件对象
try {
RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
accessFile.seek(start);// 设置操作文件的入点
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
// 指定网络位置从什么位置开始下载,到什么位置结束
conn.setRequestProperty("Range", "bytes=" + start + "-" + end + "");
InputStream in = conn.getInputStream();// 获得输入流
byte[] data = new byte[1024];
int len = 0;
while ((len = in.read(data)) != -1) {
accessFile.write(data, 0, len);
}
accessFile.close();
in.close();
System.out.println("线程:" + id + "下载完成!");
} catch (Exception e) {
e.printStackTrace();
}
}
}