现在很多下载软件像迅雷,旋风等都使用了多线程下载技术。比起单线程下载多线程下载在同一时间段内发出多个下载请求,每个下载请求负责下载一段内容,充分利用了网络宽带。
因此本文就简单的通过实例来介绍怎么用多线程去下载文件。相信您看了这篇文章后也可以写个属于自己的下载软件。
首先我们需要考虑几个难题
1.如何获取远程文件的尺寸,这关系到开启多个下载线程。本代码采用比较简单的线程数决策策略。固定每个线程分担的字节数任务,根据远程文件尺寸来决定需要开启的下载线程。
2.如何实现分工下载,即每个线程只下载远程文件的一段。这是多线程下载的核心技术。
3.如何存储,组织各个线程下载得到的文件碎片,最后将其拼成一个完整的文件。
现在我们就来解决以上的难题:
首先第一个,获取远程文件的尺寸。在HTTP反馈报文的头(Header)部分有一些数据项,其中有一项便是Content-Length,表示的便是HTTP反馈报文的正文部分的字节数。我们经常以Post,Get等方式发起HTTP请求,实际上HTTP协议还支持以Head方式发出HTTP请求。具体后面代码中有说明。
第二个问题。HTTP有一个数据项:RANGE,它代表的是下载的字节范围,如0~1024代表从文件开始处下载到第1024个字节处。
第三个问题:使用随机文件存取技术。可以一边下载一边存储。当全部下载完成后,我们便得到了一个完整的文件拷贝。
具体就参考以下代码:
/** * DownloadThread.java * 版权所有(C) 2011 [email protected] * 创建:崔冉 2011-1-17 下午02:39:41 */ 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(); } }
/** * DownloadManager.java * 版权所有(C) 2011 [email protected] * 创建:崔冉 2011-1-17 下午03:53:20 */ 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); } }