Java多线程并发下载文件工具

Java多线程并发下载文件工具

HttpClient 出处:https://blog.csdn.net/JinglongSource/article/details/102559449

import cn.shaines.core.utils.HttpClient.Response;
import cn.shaines.core.utils.HttpClient.Response.BodyHandlers;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 学习资源:
 * https://blog.csdn.net/qq_34401512/article/details/77867576
 *
 * ConcurrentDownLoad
 *         .builder()
 *         // 设置URL
 *         .setUrl("http://117.148.175.41/cdn/pcfile/20190904/16/58/GeePlayerSetup_app.exe?dis_k=26cbb7b142c2446397843d6543da209ae&dis_t=1573620667&dis_dz=CMNET-GuangDong&dis_st=36")
 *         // 设置线程每次请求的大小
 *         .setBlockSize(1024)
 *         // 设置线程数量
 *         .setThreadCount(10)
 *         // 设置保存路径
 *         .setPath("C:\\Users\\houyu\\Desktop\\GeePlayerSetup_app_my33333333.exe")
 *         // 设置存在是否删除
 *         .setDeleteIfExist(true)
 *         // 创建
 *         .build()
 *         // 开始
 *         .start((msg, total, current, speed) -> {});
 *
 * @description 并发下载文件工具
 * @date 2019-11-12 14:35:26
 * @author houyu for.houyu@foxmail.com
 */
public class ConcurrentDownLoad {

    private static final Logger log = LoggerFactory.getLogger(ConcurrentDownLoad.class);

    private Builder builder;
    /** HttpClient */
    private HttpClient httpClient;
    /** 线程池 */
    private ThreadPoolExecutor poolExecutor;
    /** 信号量 */
    private Semaphore semaphore;
    /** CountDownLatch */
    private CountDownLatch countDownLatch;
    /** 总长度 */
    private long total;
    /** 当前的进度 */
    private AtomicLong current;
    /** 回调方法 */
    private Callback callback;

    public static Builder builder() {
        return new Builder();
    }

    protected ConcurrentDownLoad(Builder builder) {
        this.builder = builder;
        httpClient = HttpClient.buildHttpClient();
        poolExecutor = new ThreadPoolExecutor(builder.threadCount, builder.threadCount,0L,TimeUnit.SECONDS,
                                              new LinkedBlockingQueue<>(), new AbortPolicy());
    }

    private Long getContentLength() {
        Response<Long> response = httpClient.buildRequest(this.builder.url).GET().execute((request, http) -> {
            if(http.getResponseCode() == HttpURLConnection.HTTP_OK) {
                return http.getContentLengthLong();
            }
            return null;
        });
        return response.getBody();
    }

    private List<long[]> getPaces(long totalLength) {
        List<long[]> paces = new ArrayList<>(16);
        long currentLength = totalLength;
        long startIndex = 0;
        long endIndex;
        while(currentLength > 0) {
            long size = currentLength >= this.builder.blockSize ? this.builder.blockSize : currentLength;
            endIndex = startIndex + size;
            endIndex = endIndex >= totalLength ? totalLength : endIndex;
            paces.add(new long[]{startIndex, endIndex});
            currentLength = currentLength - size;
            startIndex = endIndex + 1;
        }
        return paces;
    }

    public void start(Callback call) {
        this.run((msg, total, current, speed) -> {
            log.debug("msg:{} total:{} current:{} speed:{}", msg, total, current, speed);
            call.accept(msg, total, current, speed);
        });
    }

    public void start() {
        this.start((msg, total, current, speed) -> {});
    }

    private void run(Callback call) {
        try {
            this.callback = call;
            callback.accept("start...", 0, 0, 0);
            Long totalLength = getContentLength();
            if(totalLength == null) {
                callback.accept("获取文件的长度失败", 0, 0, 0);
                throw new RuntimeException("获取文件的长度失败");
            }
            total = totalLength;
            callback.accept(String.format("文件总长度:%s字节(B)", total), total, 0, 0);
            //
            this.builder.setBlockSize(this.builder.blockSize >= totalLength ? totalLength : this.builder.blockSize);
            //
            File file = new File(this.builder.path);
            if(file.exists()) {
                callback.accept("文件存在", total, 0, 0);
                if(builder.keepOnIfDisconnect && new File(this.builder.path + ".conf").exists()) {
                    // 支持断点
                    // String conf = Files.readString(Paths.get(this.builder.path + ".conf"), Charset.forName("UTF-8"));
                    // String[] split = conf.split(";");
                    // for(String s : split) {
                    //     s.split("-")
                    // }
                    // continueList.add()
                } else {
                    if(builder.deleteIfExist) {
                        file.delete();
                        callback.accept("删除文件", total, 0, 0);
                        initFile(totalLength);
                    }
                }
            } else {
                callback.accept("文件不存在, 创建目录", total, 0, 0);
                file.getParentFile().mkdirs();
                initFile(totalLength);
            }
            //
            semaphore = new Semaphore(this.builder.threadCount);
            current = new AtomicLong(0);
            List<long[]> paces = getPaces(totalLength);
            countDownLatch = new CountDownLatch(paces.size());
            for(long[] pace : paces) {
                callback.accept(String.format("pace:%s - %s", pace[0], pace[1]), total, 0, 0);
                poolExecutor.submit(new DownLoadThread(pace[0], pace[1]));
            }
            try {
                countDownLatch.await();
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
            poolExecutor.shutdown();
            callback.accept(String.format("下载完成:%s", this.builder.url), total, current.get(), 0);
        } catch(Exception e) {
            callback.accept(e.getMessage(), total, current.get(), 0);
        }

    }

    private void initFile(Long totalLength) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(this.builder.path, "rwd");
        // 指定创建的文件的长度
        raf.setLength(totalLength);
        raf.close();
    }

    /**
     * 内部类用于实现下载并组装
     */
    private class DownLoadThread implements Runnable {
        /** 下载起始位置 */
        private long startIndex;
        /** 下载结束位置 */
        private long endIndex;

        public DownLoadThread(long startIndex, long endIndex) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        @Override
        public void run() {
            try (RandomAccessFile file = new RandomAccessFile(builder.path, "rwd")) {
                semaphore.acquire();
                file.seek(startIndex);
                httpClient.buildRequest(builder.url)
                        // 添加请求头
                        .addHeader("Range", "bytes=" + startIndex + "-" + endIndex)
                        // 执行请求
                        // .execute(BodyHandlers.ofCallbackByteArray(file::write))
                        .execute(BodyHandlers.ofCallbackByteArray((data, index, length) -> {
                            file.write(data, index, length);
                            callback.accept("download...", total, current.addAndGet(length), 0);
                        }))
                ;
            } catch(Exception e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
                semaphore.release();
            }
        }
    }

    public static class Builder {
        /** 同时下载的线程数*/
        private int threadCount = 5;
        /** 每个线程每次执行的文件大小(b) 0.5M */
        private long blockSize = 1024 * 512;
        /** 服务器请求路径 */
        private String url;
        /** 本地路径 */
        private String path;
        /** 存在是否删除 */
        private boolean deleteIfExist = false;
        /** 是否断点续传 */
        private boolean keepOnIfDisconnect = true;

        public Builder setThreadCount(int threadCount) {
            this.threadCount = threadCount;
            return this;
        }
        public Builder setBlockSize(long blockSizeOfKb) {
            this.blockSize = blockSizeOfKb * 1024;
            return this;
        }
        public Builder setUrl(String url) {
            this.url = url;
            return this;
        }
        public Builder setPath(String path) {
            this.path = path;
            return this;
        }
        public Builder setDeleteIfExist(boolean deleteIfExist) {
            this.deleteIfExist = deleteIfExist;
            return this;
        }
        public Builder setKeepOnIfDisconnect(boolean keepOnIfDisconnect) {
            this.keepOnIfDisconnect = keepOnIfDisconnect;
            return this;
        }

        public ConcurrentDownLoad build() {
            return new ConcurrentDownLoad(this);
        }
    }

    public interface Callback {
        /**
         * 回调方法
         * @param msg 消息
         * @param total 总量
         * @param current 当前量
         * @param speed 速度(k/s) 暂时不实现
         */
        void accept(String msg, long total, long current, long speed);
    }



}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
zhuaxia 可以从 xiami.com 和 music.163.com 下载 MP3 文件和音频。zhuaxia 是一个基于命令行的虾米音乐 ( www.xiami.com 以下简称[虾])和网易云音乐( music.163.com 以下简称[易]) 多线程批量下载工具zhuaxia 的开发调试环境:python 2.7.6依赖requests modulemutagen modulebeautifulsoup4 module特性自动识别解析URL. 目前支持:[虾] 歌曲,专辑,精选集,用户收藏[todo], 歌手热门[易] 歌曲,专辑,歌单,歌手热门下载歌手热门歌曲:数量可配置( 海外IP下载xiami资源" 一节加入实验性-p选项,尝试解决频繁请求被服务器ban的问题中英文命令行界面. 配置项 lang=en|cn 默认中文(cn)InstallationArchlinux 用户, zhuaxia可以从AUR中获取稳定版本(master branch):稳定版本:yaourt -S zhuaxia最新git版本(bleeding branch):yaourt -S zhuaxia-git其他用户:sudo python setup.py installUsage配置文件, 第一次运行zx后, 在$HOME/.zhuaxia/ 会有配置文件 zhuaxia.conf 配置参数有中文说明使用:zhuaxia (抓虾) -- 抓取[虾米音乐]和[网易云音乐]的 mp3 音乐 [CONFIG FILE:]   $HOME/.zhuaxia/zhuaxia.conf [OPTIONS]      -H : 首选HQ质量(320kbps),          > 虾米音乐  网易音乐 <             -无需特殊要求,直接下载高音质资源     -p : (实验性选项)使用代理池下载         在下载/解析量大的情况下,目标服务器会对禁止频繁的请求,所以zhuaxia可以自动获取         代理来解析和下载资源。因为获取的代理速度/可靠性不一,下载可能会缓慢或不稳定。     -h :显示帮助     -f :从文件下载     -v :显示版本信息 [USAGE]      zx [OPTION]          : 下载指定URL资源, 抓虾自动识别链接, 支持             - [虾] 歌曲,专辑,精选集,用户收藏,艺人TopN             - [易] 歌曲,专辑,歌单,艺人TopN         例子:            zx "http://www.xiami.com/space/lib-song/u/25531126"           zx "http://music.163.com/song?id=27552647"     zx [OPTION] -f           : 多个URL在一个文件中,每个URL一行。 URLs可以是混合[虾]和[易]的不同类型音乐资源。例子:           $ cat /tmp/foo.txt             http://music.163.com/artist?id=5345             http://www.xiami.com/song/1772130322             http://music.163.com/album?id=2635059             http://www.xiami.com/album/32449           $ zx -f /tmp/foo.txtProxy setting海外IP下载xiami资源xiami.com屏蔽了海外ip的http请求。在配置文件中添加(如果不存在的话)xiami.proxy.http=ip:port 可以让zhuaxia通过代理来解析xiami资源。 例如:xiami.proxy.http=127.0.0.1:8080这里ip:port构成的http代理是国内的代理服务器。 如果你的机器已经是国内的ip,请注释或删除这个选项。获取国内代理的简单方法:到http://proxy-list.org/ 搜索China的代理就好。Screenshotsdownloading (gif animation) parse input file parse url 标签:zhuaxia
Java多线程实战项目是一个利用Java语言的多线程机制来实现并发任务处理的项目。在这样的项目中,我们可以使用多线程来将一个任务分解为多个子任务,并且将这些子任务同时执行,以提高系统的并发能力和任务处理的效率。 在实战项目中,我们可以利用多线程来实现一些需要大量任务处理的场景,比如数据的批量处理、网络请求的并发处理、大规模数据计算等等。以下是一个简单的示例: 假设我们要编写一个程序,从一个文本文件中读取一系列URL,并同时发送网络请求获取每个URL对应的网页内容。我们可以将这个任务分解为多个子任务,每个子任务负责发送一个网络请求并返回结果。我们可以使用多线程机制来同时创建多个子线程来执行这些子任务。 首先,我们需要创建一个任务管理器,用于管理任务队列。然后,我们可以创建多个子线程,并将这些子线程注册到任务管理器中。每个子线程会从任务管理器中获取一个任务,并执行该任务。 在任务执行过程中,我们可以利用Java提供的多线程工具,比如CountDownLatch和ExecutorService来控制任务的并发执行和等待所有任务执行完成。 当所有的任务执行完成后,我们可以将每个子线程的执行结果进行整合,并进行相应的处理,比如将结果写入到文件中或者将结果进行展示。 通过这个简单的示例,我们可以看到,在Java多线程实战项目中,我们可以充分利用Java多线程机制来提高程序的并发能力和任务处理的效率。当我们遇到需要处理大量任务的场景时,多线程机制可以帮助我们分解任务,同时执行,从而提高系统的响应速度和处理能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值