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

思想

  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();
    }
}
posted @ 2016-12-08 19:34  吴昭  阅读(222)  评论(0编辑  收藏  举报