基于HTTP的多线程文件下载功能实现

来源:互联网 发布:开票软件为何启动不了 编辑:程序博客网 时间:2024/04/28 20:47

思想

  1. 文件信息获取的获取方式与单线程的方式一样
  2. 与单线程相比不同的是将远程文件分块并发获取,然后再并发写入到本地暂存文件中
  3. 远程文件分块的实现依据是:connection.setRequestProperty(“Range”,”bytes=”+start+”-“+end)
  4. 本地将文件写入指定位置的实现依据是:RandomAccessFile

代码实现

package org.hanmeis;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import java.util.*;import java.util.concurrent.*;import java.util.concurrent.atomic.AtomicLong;import java.util.logging.Logger;/** * Created by zhao.wu on 2016/12/7. * 多线程下载器 * 1. 使用{@see RandomAccessFile}在文件任意位置写入内容 * 2. 使用 {@code connection.setRequestProperty("Range","bytes="+start+"-"+end);}设置从服务器上读取的文件块。 */public class MuiltDownloader {    private static Logger logger = Logger.getLogger("MuiltDownloader");    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {        String url = "http://dlsw.baidu.com/sw-search-sp/soft/e7/40642/Git-2.7.2-64-bit_setup.1457942968.exe";        MuiltDownloader muiltDownloader = new MuiltDownloader(url, 10);        muiltDownloader.connectServer();        muiltDownloader.startDownload();    }    private AtomicLong currentSize = new AtomicLong(0l);    private long preSize;    private long fileSize;    private String realFileName;    private String tempFileName;    private String urlStr;    private int taskNum;    private Timer timer = new Timer();    private MuiltDownloader(String urlStr, int taskNum) {        logger.info("新任务加入下载队列......");        this.urlStr = urlStr;        this.taskNum = taskNum;        this.tempFileName = UUID.randomUUID().toString().replace("-", "");    }    private void connectServer() throws IOException {        URL url = new URL(urlStr);        logger.info(String.format("连接服务器:%s://%s",url.getProtocol(),url.getHost()));        HttpURLConnection connection = (HttpURLConnection) url.openConnection();        connection.setConnectTimeout(10 * 1000);        this.fileSize = Long.parseLong(connection.getHeaderField("Content-Length"));        String[] temp = url.getFile().split("/");        if (temp.length != 0) {            this.realFileName = temp[temp.length - 1];        }        logger.info(String.format("文件名:%s  大小:%.2fM", this.realFileName, this.fileSize*1.0/1024/1024));        RandomAccessFile randomAccessFile = new RandomAccessFile(tempFileName,"rw");        randomAccessFile.setLength(fileSize);        randomAccessFile.close();    }    private void startDownload() throws InterruptedException, ExecutionException {        long foot = fileSize / taskNum;        long rest = fileSize % taskNum;        List<BlockDownLoadTask> tasks = new ArrayList<>();        for (int i = 0; i < taskNum; i++) {            logger.info(String.format("初始化第%d个下载线程", i+1));            BlockDownLoadTask task;            if (i < taskNum - 1) {                task = new BlockDownLoadTask(tempFileName, urlStr, i * foot, (i + 1) * foot - 1, currentSize);            } else {                task = new BlockDownLoadTask(tempFileName, urlStr, i * foot, fileSize, currentSize);            }            tasks.add(task);        }        logger.info(String.format("开始下载:%s",realFileName));        info();        ExecutorService service = Executors.newFixedThreadPool(taskNum);        service.invokeAll(tasks);        service.shutdownNow();        timer.cancel();        File file = new File(tempFileName);        file.renameTo(new File(realFileName));        logger.info(String.format("完成下载:%s",realFileName));    }    /**     * 定时计算下载状态     */    private void info(){        timer.schedule(new TimerTask() {            @Override            public void run() {                long tempC = currentSize.get();                long tempSize = tempC-preSize;                preSize = tempC;                double percent = (tempC*100.0)/fileSize;                double speed = (tempSize*1.0)/1024/3;                logger.info(String.format("文件:%s 已完成:%.2f%%  速率:%.2fkb/s",realFileName, percent, speed));            }        },0, 3000);    }}class BlockDownLoadTask implements Callable<String> {    private String tempFileName;    private String urlStr;    private long startPosition;    private long endPosition;    private AtomicLong currentSize;    BlockDownLoadTask(String tempFileName, String urlStr, long startPosition, long endPosition, AtomicLong currentSize) {        this.tempFileName = tempFileName;        this.urlStr = urlStr;        this.startPosition = startPosition;        this.endPosition = endPosition;        this.currentSize = currentSize;    }    @Override    public String call() throws Exception {        URL url = new URL(urlStr);        HttpURLConnection connection = (HttpURLConnection) url.openConnection();        connection.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);        InputStream is = connection.getInputStream();        RandomAccessFile randomAccessFile = new RandomAccessFile(tempFileName, "rwd");        randomAccessFile.seek(startPosition);        byte[] bs = new byte[1024];        int len;        while ((len = is.read(bs)) > 0) {            randomAccessFile.write(bs);            currentSize.addAndGet(len);        }        randomAccessFile.close();        is.close();        return Thread.currentThread().getName();    }}
0 0