还是利用TDD(测试驱动的方式)来编码实现多线程下载
需求:使用多线程实现下载,所要实现的接口
(1)ConnectionManager,可以打开一个连接,通过Connection可以读取其中的一段(用startPos,endPos来指定)
(2)DownloadListener,由于是多线程下载,调用这个类的客户端不知道什么时候结束,故需要当所有的线程都执行完以后,调用listener的notifiedFinished方法,这样客户端就能收到通知。
具体的实现思路:
1、需要调用ConnectionManager的open方法打开连接,然后通过Connection.getContentLength方法获得文件的长度
2、至少启动三个线程下载,注意每个线程需要先调用ConnectionManager的open方法。
3、把byte数组写入到文件中
4、所有的线程都下载完成以后,需要调用listener的notifiedFinished方法。
以测试驱动开始
public class ConnectionTest {
@Test
public void testContentLength() throws Exception{
ConnectionManager connMan=new ConnectionManagerImpl();
Connection conn=connMan.open("http://www.hinews.cn/pic/0/13/91/26/13912621_821796.jpg");
Assert.assertEquals(35470, conn.getContentLength());
}
@Test
public void testRead() throws Exception{
ConnectionManager connMan=new ConnectionManagerImpl();
Connection conn =connMan.open("http://www.hinews.cn/pic/0/13/91/26/13912621_821796.jpg");
byte[] data =conn.read(0, 35469);
Assert.assertEquals(35470, data.length);
data=conn.read(1024, 2023);
Assert.assertEquals(1000, data.length);
}
}
ConnectionManager是一个接口,具体实现类是在ConnectionManagerImpl类中
public interface ConnectionManager {
/**
* 给定一个URL,打开一个连接
* @param url
* @return
*/
public Connection open(String url) throws ConnectionException;
}
public class ConnectionManagerImpl implements ConnectionManager{
@Override
public Connection open(String url) {
try {
return new ConnectionImpl(url); //返回Connection的包装类,其中就包含了URL
} catch (download.impl.ConnectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
//具体的方法实现是在ConnectionImpl类中
public interface Connection {
/**
* 给定开始和结束位置,读取数据,返回值是字节数组
* @param startPos 开始位置 从0开始
* @param endPos 结束位置
* @return
*/
public byte[] read(int startPos,int endPos)throws IOException;
/**
* 得到数据的内容
* @return
*/
public int getContentLength();
/**
* 关闭连接
*/
public void close();
}
public class ConnectionImpl implements Connection{
URL url;
static final int BUFFER_SIZE=1024;
public ConnectionImpl(String urlLocation) throws ConnectionException{
try {
url=new URL(urlLocation);
} catch (MalformedURLException e) {
e.printStackTrace();
throw new ConnectionException(e);
}
}
public byte[] read(int startPos,int endPos) throws IOException{ //为了ConnectionTest中的测试通过,实现
HttpURLConnection httpConn=(HttpURLConnection) url.openConnection();
httpConn.setRequestProperty("Range", "bytes"+startPos+"-"+endPos); //HTTP1.1以后可以指定资源从哪起止开始下载
InputStream is=httpConn.getInputStream();
byte[] buff=new byte[BUFFER_SIZE];
int totalLen=endPos-startPos+1;
ByteArrayOutputStream boas=new ByteArrayOutputStream();
while(boas.size()<totalLen){
int len=is.read(buff);
if(len<0){
break;
}
boas.write(buff,0,len);
}
if(boas.size()>totalLen){
byte[] data=boas.toByteArray();
return Arrays.copyOf(data, totalLen);//因为可能多次利用buff数组,最后的一次可能需要的小于1024
}
return boas.toByteArray();
}
@Override
public int getContentLength() { //为了ConnectionTest中的测试通过,实现。
URLConnection con;
try {
con=url.openConnection();
return con.getContentLength();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
@Override
public void close() {
}
}
public class FileDownloaderTest {
boolean downloadFinish=false;
@Test
public void testDownload(){
String url="http://www.hinews.cn/pic/0/13/91/26/13912621_821796.jpg";
FileDownloader downloader=new FileDownloader(url,"E:\\test\\temp.jpg");
ConnectionManager cm=new ConnectionManagerImpl();
downloader.setConnectionManager(cm);
downloader.setListener(new DownloadListener(){
@Override
public void notifyFinished() {
downloadFinish=true;
}
});
downloader.excute();
while(!downloadFinish){
try{
System.out.println("还没有下载完成,休眠五秒");
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println("下载完成");
}
}
具体的线程下载的类:
public class FileDownloader {
private String url;
private String localFile;
DownloadListener listener;
ConnectionManager cm;
private static final int DOWNLOAD_THREAD_NUM = 3;
public FileDownloader(String _url, String localfile) {
this.url = _url;
this.localFile = localfile;
}
public void excute() {
//启动三个线程负责下载,主线程作为监听,资源下载完成,调用 notifyFinished()方法。
//CycliBarrier一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点,俗称栅栏类
CyclicBarrier barrier = new CyclicBarrier(DOWNLOAD_THREAD_NUM, new Runnable() {
@Override
public void run() {
listener.notifyFinished();
}
});
Connection conn = null;
try {
conn = cm.open(this.url);
int length = conn.getContentLength();
createPlaceHolderFile(this.localFile, length);
int[][] ranges = allocateDownloadRange(DOWNLOAD_THREAD_NUM, length);
for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) {
DownloadThread thread = new DownloadThread(cm.open(url), ranges[i][0], ranges[i][1], localFile,
barrier);
thread.start();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(conn!=null){
conn.close();
}
}
}
private int[][] allocateDownloadRange(int threadNum, int length) {
int[][] ranges = new int[threadNum][2];
int eachThreadSize = length / threadNum;//每个线程需要下载文件的大小
int last = length % threadNum;//剩下的归最后一个线程来处理
for (int i = 0; i < threadNum; i++) {
int startPos = i * eachThreadSize;
int endPos = (i + 1) * eachThreadSize - 1;
if ((i == (threadNum - 1))) {
endPos+= last;
}
ranges[i][0] = startPos;
ranges[i][1] = endPos;
}
return ranges;
}
private void createPlaceHolderFile(String filename, int contentlen) throws IOException {
RandomAccessFile file = new RandomAccessFile(filename, "rw"); //新建一个文件来保存下载的资源
for (int i = 0; i < contentlen; i++) {
file.write(0);
}
file.close();
}
public void setListener(DownloadListener listener) {
this.listener = listener;
}
public void setConnectionManager(ConnectionManager ucm) {
this.cm = ucm;
}
public DownloadListener getListener() {
return listener;
}
}