TDD(一)

还是利用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;
    }
}



阅读更多

没有更多推荐了,返回首页