实战多线程下载
现在很多下载软件像迅雷,旋风等都使用了多线程下载技术。比起单线程下载多线程下载在同一时间段内发出多个下载请求,每个下载请求负责下载一段内容,充分利用了网络宽带。
因此本文就简单的通过实例来介绍怎么用多线程去下载文件。相信您看了这篇文章后也可以写个属于自己的下载软件。
首先我们需要考虑几个难题
1.如何获取远程文件的尺寸,这关系到开启多个下载线程。本代码采用比较简单的线程数决策策略。固定每个线程分担的字节数任务,根据远程文件尺寸来决定需要开启的下载线程。
2.如何实现分工下载,即每个线程只下载远程文件的一段。这是多线程下载的核心技术。
3.如何存储,组织各个线程下载得到的文件碎片,最后将其拼成一个完整的文件。
现在我们就来解决以上的难题:
首先第一个,获取远程文件的尺寸。在HTTP反馈报文的头(Header)部分有一些数据项,其中有一项便是Content-Length,表示的便是HTTP反馈报文的正文部分的字节数。我们经常以Post,Get等方式发起HTTP请求,实际上HTTP协议还支持以Head方式发出HTTP请求。具体后面代码中有说明。
第二个问题。HTTP有一个数据项:RANGE,它代表的是下载的字节范围,如0~1024代表从文件开始处下载到第1024个字节处。
第三个问题:使用随机文件存取技术。可以一边下载一边存储。当全部下载完成后,我们便得到了一个完整的文件拷贝。
具体就参考以下代码:
package com.cayden.thread741;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author 崔冉
* @version 1.0.0
* @desc
*/
public class DownloadThread extends Thread{
private String url=null;//待下载的文件
private String file=null;//本地存储路径
private long offset=0;//偏移量
private long length=0;//分配给本线程的下载字节数
public DownloadThread(String url,String file,long offset,long length){
this.url=url;
this.file=file;
this.offset=offset;
this.length=length;
System.out.println("偏移量="+offset+";字节数="+length);
}
public void run(){
try{
HttpURLConnection httpURLConnection=(HttpURLConnection)new URL(this.url).openConnection();
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setRequestProperty("RANGE", "bytes="+this.offset+"-"+(this.offset+this.length-1));
BufferedInputStream bis=new BufferedInputStream(httpURLConnection.getInputStream());
byte[] buff=new byte[102400];
int bytesRead;
while(-1!=(bytesRead=bis.read(buff,0,buff.length))){
this.writeFile(this.file, this.offset, buff, bytesRead);
this.offset=this.offset+bytesRead;
}
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
/**
* 将字节数组以随机存取方式写入文件
* @param fileName是被写入的文件
* @param offset代表写入文件的位置偏移量
* @param bytes待写入的字节数组
* @param realLength实际需要写入的字节数
* @throws IOException
*/
public void writeFile(String fileName,long offset,byte[] bytes,int realLength) throws IOException{
File newFile=new File(fileName);
RandomAccessFile raf=new RandomAccessFile(newFile,"rw");
raf.seek(offset);
raf.write(bytes,0,realLength);
raf.close();
}
}
package com.cayden.thread741;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author 崔冉
* @version 1.0.0
* @desc
*/
public class DownloadManager {
static final long unitSize=500*1024;//分配给每个下载线程的字节数
public void doDownload(String remoteFileUrl,String localFileName) throws IOException{
long fileSize=this.getRemoteFileSize(remoteFileUrl);
this.createFile(localFileName, fileSize);
long threadCount=fileSize/unitSize;
System.out.println("共启动线程"+(fileSize%unitSize==0?threadCount:threadCount+1)+"个");
long offset=0;
if(fileSize<=unitSize){//如果远程文件尺寸小于等于unitSize
DownloadThread downloadThread=new DownloadThread(remoteFileUrl,localFileName,offset,unitSize);
downloadThread.start();
}else{
//如果远程文件尺寸大于unitSize
for(int i=1;i<=threadCount;i++){
DownloadThread downloadThread=new DownloadThread(remoteFileUrl,localFileName,offset,unitSize);
downloadThread.start();
offset=offset+unitSize;
}
if(fileSize%unitSize!=0){//如果不能整除,则需要再创建一个线程下载剩余字节。
DownloadThread downloadThread=new DownloadThread(remoteFileUrl,localFileName,offset,fileSize-unitSize*threadCount);
downloadThread.start();
}
}
}
/**
* 获取远程文件尺寸
* @param remoteFileUrl
* @return
* @throws IOException
*/
private long getRemoteFileSize(String remoteFileUrl) throws IOException{
long result=0;
HttpURLConnection httpURLConnection=(HttpURLConnection)new URL(remoteFileUrl).openConnection();
httpURLConnection.setRequestMethod("HEAD");
for(int i=1;i<=10;i++){
if(httpURLConnection.getHeaderFieldKey(i).equalsIgnoreCase("Content-Length"))
{
result=Long.parseLong(httpURLConnection.getHeaderField(i));
break;
}
}
return result;
}
/**
* 创建指定大小的文件
* @param fileName
* @param fileSize
* @throws IOException
*/
private void createFile(String fileName,long fileSize) throws IOException{
File newFile=new File(fileName);
RandomAccessFile raf=new RandomAccessFile(newFile,"rw");
raf.setLength(fileSize);
raf.close();
}
/**
* @param args
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// if(args.length!=2){
// System.out.println("Usage java DownloadManager URL local_file_name");
// return;
// }
DownloadManager downloadManager=new DownloadManager();
String remoteFileUrl="http://blog.xbsk.com/UploadFiles/2011-1/113260191.mp3";
String localFileName="C:/4.mp3";
downloadManager.doDownload(remoteFileUrl, localFileName);
}
}