java 多线程断点下载文件

下载文件信息类、实体

封装即将下载资源的信息

package com.hoo.entity;



/**

* <b>function:</b> 下载文件信息类

* @author hoojo

* @createDate 2011-9-21 下午05:14:58

* @file DownloadInfo.java

* @package com.hoo.entity

* @project MultiThreadDownLoad

* @blog http://blog.csdn.net/IBM_hoojo

* @email [email protected]

* @version 1.0

*/

public class DownloadInfo {

    //下载文件url

    private String url;

    //下载文件名称

    private String fileName;

    //下载文件路径

    private String filePath;

    //分成多少段下载, 每一段用一个线程完成下载

    private int splitter;

   

    //下载文件默认保存路径

    private final static String FILE_PATH = "C:/temp";

    //默认分块数、线程数

    private final static int SPLITTER_NUM = 5;

   

    public DownloadInfo() {

        super();

    }

   

    /**

     * @param url 下载地址

     */

    public DownloadInfo(String url) {

        this(url, null, null, SPLITTER_NUM);

    }

   

    /**

     * @param url 下载地址url

     * @param splitter 分成多少段或是多少个线程下载

     */

    public DownloadInfo(String url, int splitter) {

        this(url, null, null, splitter);

    }

   

    /***

     * @param url 下载地址

     * @param fileName 文件名称

     * @param filePath 文件保存路径

     * @param splitter 分成多少段或是多少个线程下载

     */

    public DownloadInfo(String url, String fileName, String filePath, int splitter) {

        super();

        if (url == null || "".equals(url)) {

            throw new RuntimeException("url is not null!");

        }

        this.url =  url;

        this.fileName = (fileName == null || "".equals(fileName)) ? getFileName(url) : fileName;

        this.filePath = (filePath == null || "".equals(filePath)) ? FILE_PATH : filePath;

        this.splitter = (splitter < 1) ? SPLITTER_NUM : splitter;

    }

   

    /**

     * <b>function:</b> 通过url获得文件名称

     * @author hoojo

     * @createDate 2011-9-30 下午05:00:00

     * @param url

     * @return

     */

    private String getFileName(String url) {

        return url.substring(url.lastIndexOf("/") + 1, url.length());

    }

   

    public String getUrl() {

        return url;

    }



    public void setUrl(String url) {

        if (url == null || "".equals(url)) {

            throw new RuntimeException("url is not null!");

        }

        this.url = url;

    }



    public String getFileName() {

        return fileName;

    }



    public void setFileName(String fileName) {

        this.fileName = (fileName == null || "".equals(fileName)) ? getFileName(url) : fileName;

    }



    public String getFilePath() {

        return filePath;

    }



    public void setFilePath(String filePath) {

        this.filePath = (filePath == null || "".equals(filePath)) ? FILE_PATH : filePath;

    }



    public int getSplitter() {

        return splitter;

    }



    public void setSplitter(int splitter) {

        this.splitter = (splitter < 1) ? SPLITTER_NUM : splitter;

    }

   

    @Override

    public String toString() {

        return this.url + "#" + this.fileName + "#" + this.filePath + "#" + this.splitter;

    }

}




二、随机写入一段文件

package com.hoo.download;



import java.io.IOException;

import java.io.RandomAccessFile;



/**

* <b>function:</b> 写入文件、保存文件

* @author hoojo

* @createDate 2011-9-21 下午05:44:02

* @file SaveItemFile.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @blog http://blog.csdn.net/IBM_hoojo

* @email [email protected]

* @version 1.0

*/

public class SaveItemFile {

    //存储文件

    private RandomAccessFile itemFile;

   

    public SaveItemFile() throws IOException {

        this("", 0);

    }

   

    /**

     * @param name 文件路径、名称

     * @param pos 写入点位置 position

     * @throws IOException

     */

    public SaveItemFile(String name, long pos) throws IOException {

        itemFile = new RandomAccessFile(name, "rw");

        //在指定的pos位置开始写入数据

        itemFile.seek(pos);

    }

   

    /**

     * <b>function:</b> 同步方法写入文件

     * @author hoojo

     * @createDate 2011-9-26 下午12:21:22

     * @param buff 缓冲数组

     * @param start 起始位置

     * @param length 长度

     * @return

     */

    public synchronized int write(byte[] buff, int start, int length) {

        int i = -1;

        try {

            itemFile.write(buff, start, length);

            i = length;

        } catch (IOException e) {

            e.printStackTrace();

        }

        return i;

    }

   

    public void close() throws IOException {

        if (itemFile != null) {

            itemFile.close();

        }

    }

}

这个类主要是完成向本地的指定文件指针出开始写入文件,并返回当前写入文件的长度(文件指针)。这个类将被线程调用,文件被分成对应的块后,将被线程调用。每个线程都将会调用这个类完成文件的随机写入。


三、单个线程下载文件

package com.hoo.download;



import java.io.IOException;

import java.io.InputStream;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;

import java.net.URLConnection;

import com.hoo.util.LogUtils;



/**

* <b>function:</b> 单线程下载文件

* @author hoojo

* @createDate 2011-9-22 下午02:55:10

* @file DownloadFile.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @blog http://blog.csdn.net/IBM_hoojo

* @email [email protected]

* @version 1.0

*/

public class DownloadFile extends Thread {

   

    //下载文件url

    private String url;

    //下载文件起始位置 

    private long startPos;

    //下载文件结束位置

    private long endPos;

    //线程id

    private int threadId;

   

    //下载是否完成

    private boolean isDownloadOver = false;



    private SaveItemFile itemFile;

   

    private static final int BUFF_LENGTH = 1024 * 8;

   

    /**

     * @param url 下载文件url

     * @param name 文件名称

     * @param startPos 下载文件起点

     * @param endPos 下载文件结束点

     * @param threadId 线程id

     * @throws IOException

     */

    public DownloadFile(String url, String name, long startPos, long endPos, int threadId) throws IOException {

        super();

        this.url = url;

        this.startPos = startPos;

        this.endPos = endPos;

        this.threadId = threadId;

        //分块下载写入文件内容

        this.itemFile = new SaveItemFile(name, startPos);

    }



   

    @Override

    public void run() {

        while (endPos > startPos && !isDownloadOver) {

            try {

                URL url = new URL(this.url);

                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

               

                // 设置连接超时时间为10000ms

                conn.setConnectTimeout(10000);

                // 设置读取数据超时时间为10000ms

                conn.setReadTimeout(10000);

               

                setHeader(conn);

               

                String property = "bytes=" + startPos + "-";

                conn.setRequestProperty("RANGE", property);

               

                //输出log信息

                LogUtils.log("开始 " + threadId + ":" + property + endPos);

                //printHeader(conn);

               

                //获取文件输入流,读取文件内容

                InputStream is = conn.getInputStream();

               

                byte[] buff = new byte[BUFF_LENGTH];

                int length = -1;

                LogUtils.log("#start#Thread: " + threadId + ", startPos: " + startPos + ", endPos: " + endPos);

                while ((length = is.read(buff)) > 0 && startPos < endPos && !isDownloadOver) {

                    //写入文件内容,返回最后写入的长度

                    startPos += itemFile.write(buff, 0, length);

                }

                LogUtils.log("#over#Thread: " + threadId + ", startPos: " + startPos + ", endPos: " + endPos);

                LogUtils.log("Thread " + threadId + " is execute over!");

                this.isDownloadOver = true;

            } catch (MalformedURLException e) {

                e.printStackTrace();

            } catch (IOException e) {

                e.printStackTrace();

            } finally {

                try {

                    if (itemFile != null) {

                        itemFile.close();

                    }

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        }

        if (endPos < startPos && !isDownloadOver) {

            LogUtils.log("Thread " + threadId  + " startPos > endPos, not need download file !");

            this.isDownloadOver = true;

        }

        if (endPos == startPos && !isDownloadOver) {

            LogUtils.log("Thread " + threadId  + " startPos = endPos, not need download file !");

            this.isDownloadOver = true;

        }

    }

   

    /**

     * <b>function:</b> 打印下载文件头部信息

     * @author hoojo

     * @createDate 2011-9-22 下午05:44:35

     * @param conn HttpURLConnection

     */

    public static void printHeader(URLConnection conn) {

        int i = 1;

        while (true) {

            String header = conn.getHeaderFieldKey(i);

            i++;

            if (header != null) {

                LogUtils.info(header + ":" + conn.getHeaderField(i));

            } else {

                break;

            }

        }

    }

   

    /**

     * <b>function:</b> 设置URLConnection的头部信息,伪装请求信息

     * @author hoojo

     * @createDate 2011-9-28 下午05:29:43

     * @param con

     */

    public static void setHeader(URLConnection conn) {

        conn.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");

        conn.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");

        conn.setRequestProperty("Accept-Encoding", "utf-8");

        conn.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");

        conn.setRequestProperty("Keep-Alive", "300");

        conn.setRequestProperty("connnection", "keep-alive");

        conn.setRequestProperty("If-Modified-Since", "Fri, 02 Jan 2009 17:00:05 GMT");

        conn.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");

        conn.setRequestProperty("Cache-conntrol", "max-age=0");

        conn.setRequestProperty("Referer", "http://www.baidu.com");

    }

   

    public boolean isDownloadOver() {

        return isDownloadOver;

    }

   

    public long getStartPos() {

        return startPos;

    }



    public long getEndPos() {

        return endPos;

    }

}

这个类主要是完成单个线程的文件下载,将通过URLConnection读取指定url的资源信息。然后用InputStream读取文件内容,然后调用调用SaveItemFile类,向本地写入当前要读取的块的内容。


四、分段多线程写入文件内容

package com.hoo.download;



import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;

import com.hoo.entity.DownloadInfo;

import com.hoo.util.LogUtils;



/**

* <b>function:</b> 分批量下载文件

* @author hoojo

* @createDate 2011-9-22 下午05:51:54

* @file BatchDownloadFile.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @blog http://blog.csdn.net/IBM_hoojo

* @email [email protected]

* @version 1.0

*/

public class BatchDownloadFile implements Runnable {

    //下载文件信息

    private DownloadInfo downloadInfo;

    //一组开始下载位置

    private long[] startPos;

    //一组结束下载位置

    private long[] endPos;

    //休眠时间

    private static final int SLEEP_SECONDS = 500;

    //子线程下载

    private DownloadFile[] fileItem;

    //文件长度

    private int length;

    //是否第一个文件

    private boolean first = true;

    //是否停止下载

    private boolean stop = false;

    //临时文件信息

    private File tempFile;

   

    public BatchDownloadFile(DownloadInfo downloadInfo) {

        this.downloadInfo = downloadInfo;

        String tempPath = this.downloadInfo.getFilePath() + File.separator + downloadInfo.getFileName() + ".position";

        tempFile = new File(tempPath);

        //如果存在读入点位置的文件

        if (tempFile.exists()) {

            first = false;

            //就直接读取内容

            try {

                readPosInfo();

            } catch (IOException e) {

                e.printStackTrace();

            }

        } else {

            //数组的长度就要分成多少段的数量

            startPos = new long[downloadInfo.getSplitter()];

            endPos = new long[downloadInfo.getSplitter()];

        }

    }

   

    @Override

    public void run() {

        //首次下载,获取下载文件长度

        if (first) {

            length = this.getFileSize();//获取文件长度

            if (length == -1) {

                LogUtils.log("file length is know!");

                stop = true;

            } else if (length == -2) {

                LogUtils.log("read file length is error!");

                stop = true;

            } else if (length > 0) {

                /**

                 * eg

                 * start: 1, 3, 5, 7, 9

                 * end: 3, 5, 7, 9, length

                 */

                for (int i = 0, len = startPos.length; i < len; i++) {

                    int size = i * (length / len);

                    startPos[i] = size;

                   

                    //设置最后一个结束点的位置

                    if (i == len - 1) {

                        endPos[i] = length;

                    } else {

                        size = (i + 1) * (length / len);

                        endPos[i] = size;

                    }

                    LogUtils.log("start-end Position[" + i + "]: " + startPos[i] + "-" + endPos[i]);

                }

            } else {

                LogUtils.log("get file length is error, download is stop!");

                stop = true;

            }

        }

       

        //子线程开始下载

        if (!stop) {

            //创建单线程下载对象数组

            fileItem = new DownloadFile[startPos.length];//startPos.length = downloadInfo.getSplitter()

            for (int i = 0; i < startPos.length; i++) {

                try {

                    //创建指定个数单线程下载对象,每个线程独立完成指定块内容的下载

                    fileItem[i] = new DownloadFile(

                        downloadInfo.getUrl(),

                        this.downloadInfo.getFilePath() + File.separator + downloadInfo.getFileName(),

                        startPos[i], endPos[i], i

                    );

                    fileItem[i].start();//启动线程,开始下载

                    LogUtils.log("Thread: " + i + ", startPos: " + startPos[i] + ", endPos: " + endPos[i]);

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

           

            //循环写入下载文件长度信息

            while (!stop) {

                try {

                    writePosInfo();

                    LogUtils.log("downloading……");

                    Thread.sleep(SLEEP_SECONDS);

                    stop = true;

                } catch (IOException e) {

                    e.printStackTrace();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                for (int i = 0; i < startPos.length; i++) {

                    if (!fileItem[i].isDownloadOver()) {

                        stop = false;

                        break;

                    }

                }

            }

            LogUtils.info("Download task is finished!");

        }

    }

   

    /**

     * 将写入点数据保存在临时文件中

     * @author hoojo

     * @createDate 2011-9-23 下午05:25:37

     * @throws IOException

     */

    private void writePosInfo() throws IOException {

        DataOutputStream dos = new DataOutputStream(new FileOutputStream(tempFile));

        dos.writeInt(startPos.length);

        for (int i = 0; i < startPos.length; i++) {

            dos.writeLong(fileItem[i].getStartPos());

            dos.writeLong(fileItem[i].getEndPos());

            //LogUtils.info("[" + fileItem[i].getStartPos() + "#" + fileItem[i].getEndPos() + "]");

        }

        dos.close();

    }

   

    /**

     * <b>function:</b>读取写入点的位置信息

     * @author hoojo

     * @createDate 2011-9-23 下午05:30:29

     * @throws IOException

     */

    private void readPosInfo() throws IOException {

        DataInputStream dis = new DataInputStream(new FileInputStream(tempFile));

        int startPosLength = dis.readInt();

        startPos = new long[startPosLength];

        endPos = new long[startPosLength];

        for (int i = 0; i < startPosLength; i++) {

            startPos[i] = dis.readLong();

            endPos[i] = dis.readLong();

        }

        dis.close();

    }

   

    /**

     * <b>function:</b> 获取下载文件的长度

     * @author hoojo

     * @createDate 2011-9-26 下午12:15:08

     * @return

     */

    private int getFileSize() {

        int fileLength = -1;

        try {

            URL url = new URL(this.downloadInfo.getUrl());

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

           

            DownloadFile.setHeader(conn);



            int stateCode = conn.getResponseCode();

            //判断http status是否为HTTP/1.1 206 Partial Content或者200 OK

            if (stateCode != HttpURLConnection.HTTP_OK && stateCode != HttpURLConnection.HTTP_PARTIAL) {

                LogUtils.log("Error Code: " + stateCode);

                return -2;

            } else if (stateCode >= 400) {

                LogUtils.log("Error Code: " + stateCode);

                return -2;

            } else {

                //获取长度

                fileLength = conn.getContentLength();

                LogUtils.log("FileLength: " + fileLength);

            }

           

            //读取文件长度

            /*for (int i = 1; ; i++) {

                String header = conn.getHeaderFieldKey(i);

                if (header != null) {

                    if ("Content-Length".equals(header)) {

                        fileLength = Integer.parseInt(conn.getHeaderField(i));

                        break;

                    }

                } else {

                    break;

                }

            }

            */

           

            DownloadFile.printHeader(conn);

        } catch (MalformedURLException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

        return fileLength;

    }

}

这个类主要是完成读取指定url资源的内容,获取该资源的长度。然后将该资源分成指定的块数,将每块的起始下载位置、结束下载位置,分别保存在一个数组中。每块都单独开辟一个独立线程开始下载。在开始下载之前,需要创建一个临时文件,写入当前下载线程的开始下载指针位置和结束下载指针位置。


五、工具类、测试类

日志工具类

package com.hoo.util;



/**

* <b>function:</b> 日志工具类

* @author hoojo

* @createDate 2011-9-21 下午05:21:27

* @file LogUtils.java

* @package com.hoo.util

* @project MultiThreadDownLoad

* @blog http://blog.csdn.net/IBM_hoojo

* @email [email protected]

* @version 1.0

*/

public abstract class LogUtils {

   

    public static void log(Object message) {

        System.err.println(message);

    }

   

    public static void log(String message) {

        System.err.println(message);

    }

   

    public static void log(int message) {

        System.err.println(message);

    }

   

    public static void info(Object message) {

        System.out.println(message);

    }

   

    public static void info(String message) {

        System.out.println(message);

    }

   

    public static void info(int message) {

        System.out.println(message);

    }

}



下载工具类

package com.hoo.util;



import com.hoo.download.BatchDownloadFile;

import com.hoo.entity.DownloadInfo;



/**

* <b>function:</b> 分块多线程下载工具类

* @author hoojo

* @createDate 2011-9-28 下午05:22:18

* @file DownloadUtils.java

* @package com.hoo.util

* @project MultiThreadDownLoad

* @blog http://blog.csdn.net/IBM_hoojo

* @email [email protected]

* @version 1.0

*/

public abstract class DownloadUtils {



    public static void download(String url) {

        DownloadInfo bean = new DownloadInfo(url);

        LogUtils.info(bean);

        BatchDownloadFile down = new BatchDownloadFile(bean);

        new Thread(down).start();

    }

   

    public static void download(String url, int threadNum) {

        DownloadInfo bean = new DownloadInfo(url, threadNum);

        LogUtils.info(bean);

        BatchDownloadFile down = new BatchDownloadFile(bean);

        new Thread(down).start();

    }

   

    public static void download(String url, String fileName, String filePath, int threadNum) {

        DownloadInfo bean = new DownloadInfo(url, fileName, filePath, threadNum);

        LogUtils.info(bean);

        BatchDownloadFile down = new BatchDownloadFile(bean);

        new Thread(down).start();

    }

}



下载测试类

package com.hoo.test;



import com.hoo.util.DownloadUtils;



/**

* <b>function:</b> 下载测试

* @author hoojo

* @createDate 2011-9-23 下午05:49:46

* @file TestDownloadMain.java

* @package com.hoo.download

* @project MultiThreadDownLoad

* @blog http://blog.csdn.net/IBM_hoojo

* @email [email protected]

* @version 1.0

*/

public class TestDownloadMain {



    public static void main(String[] args) {

        /*DownloadInfo bean = new DownloadInfo("http://i7.meishichina.com/Health/UploadFiles/201109/2011092116224363.jpg");

        System.out.println(bean);

        BatchDownloadFile down = new BatchDownloadFile(bean);

        new Thread(down).start();*/

       

        //DownloadUtils.download("http://i7.meishichina.com/Health/UploadFiles/201109/2011092116224363.jpg");

        DownloadUtils.download("http://mp3.baidu.com/j?j=2&url=http%3A%2F%2Fzhangmenshiting2.baidu.com%2Fdata%2Fmusic%2F1669425%2F%25E9%2599%25B7%25E5%2585%25A5%25E7%2588%25B1%25E9%2587%258C%25E9%259D%25A2.mp3%3Fxcode%3D2ff36fb70737c816553396c56deab3f1", "aa.mp3", "c:/temp", 5);

    }

}

多线程下载主要在第三部和第四部,其他的地方还是很好理解。源码中提供相应的注释了,便于理解。
转http://www.cnblogs.com/hoojo/archive/2011/09/30/2196767.html

猜你喜欢

转载自rain-2372.iteye.com/blog/1342329
今日推荐