JAVA 实现多线程下载大文件

JAVA 实现多线程下载大文件

开发中遇到一个问题,下载大文件到本地,导致等待时间过长,然后就寻找一个可以多线程下载的办法,受下载软件启发,想到多线程下载,
原理,首先查到这个文件的大小,然后根据线程数量去分配每个线程下载多大的片段,然后将每个线程的组合到一起,就是最终的下载文件。如图

在这里插入图片描述

然后就是代码时间, 必不可少的控制层

    @Override
    @ApiOperation(value = "多线程获取大文件", httpMethod = "POST")
    @BodyValidate
    public Result getBigFile(@RequestBody CommonFileRequest commonFileRequest, HttpServletResponse response) {
        return commonFileService.getBigFile(commonFileRequest,response);
    }

然后是业务层

 /**
     * .
     * @Description: 作用:   多线程获取大文件
     * @Author: LXT
     * @Date: 2021/12/27 9:18
     * @param request 入参
     * @return Result
     */
    public Result getBigFile(CommonFileRequest request, HttpServletResponse response) {
        logger.info("多线程获取大文件," + JSON.toJSONString(request));
         long start = System.currentTimeMillis();
        //超大  6G
        //String path = "???";
        // 1.3G
        //String path = "???";
        //正常大小
        String path = "???";

        try {
            ServletOutputStream out = null;

            String s = BigFileDownload.BigFileTestUtil(accessKey, secretKey, endpoint, bucketName, path);
            File file = new File(s);
            InputStream fis = new BufferedInputStream(new FileInputStream(file));

            response.reset();
            // 设置response的Header
            response.addHeader("Content-Length", "" + file.length());
            response.setContentType("application/octet-stream;charset=UTF-8");
            response.setHeader("Content-disposition", "attachment;filename=" + UUID.randomUUID().toString().replace("-", "") + ".zip");

            out = response.getOutputStream();
            //读取文件流
            int len = 0;
            byte[] buffer = new byte[1024 * 10];
            while ((len = fis.read(buffer)) != -1){
                out.write(buffer,0,len);
            }
            out.flush();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        logger.info("获取大文件耗时 {}", BusinessStringUtils.formatTime(System.currentTimeMillis() - start));

        return null;
    }

解析 首先 记录一下请求的日志 然后是拿到对应的存储位置
然后去平台获取文件 BigFileDownload.BigFileTestUtil 这个方法
返回的缓存在服务器 或者本地的一个位置 然后将其返回给移动端
后边这些就不说了 都是正常的反流代码 这里着重说明 多线程分片下载


/**
 * .
 *
 * @ClassName: BigFileDownload
 * @Description:
 * @Author: LXT
 * @Date: 2021/12/27 13:01
 */
public class BigFileDownload {

    //创建一个计数器锁。初始值为线程数量,每执行结束一个线程后计数器减去1 ,当计数器为0的时候await等待的线程会被唤醒继续执行。
    public static CountDownLatch latch = new CountDownLatch(100);


    public static String BigFileTestUtil(String accessKey, String secretKey, String endpoint, String bucketName, String downloadFilePath) {
        RandomAccessFile file = null;
        String filename = "D:\\222ddddd2.zip";
        File fileDownService = new File(filename);
        if (!fileDownService.exists()) {
            try {
                fileDownService.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("文件已创建");
        } else {
            System.out.println("文件已存在");
        }
        try {
            AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
            ClientConfiguration clientConfig = new ClientConfiguration();
            clientConfig.setProtocol(Protocol.HTTP);
            AmazonS3 s3Client = new AmazonS3Client(credentials, clientConfig);
            s3Client.setEndpoint(endpoint);
            //获取对象大小
            AmazonS3 instance = AmazonS3Builder.getInstance(endpoint, accessKey, secretKey);
            S3Object object = instance.getObject(bucketName, downloadFilePath);
            S3ObjectInputStream in3 = object.getObjectContent();
            ObjectMetadata metadata = object.getObjectMetadata();
            long filesize = metadata.getInstanceLength();
            System.out.println("文件大小:" + filesize);

            ExecutorService service = Executors.newFixedThreadPool(100);
            long length = filesize;
            long packageLength = length / 100;
            long leftLength = length % 100;
            long pos = 0;
            long end = packageLength;
            file = new RandomAccessFile(filename, "rw");
            //计算每个线程请求文件的开始和结束位置
            for (int i = 0; i < 100; i++) {
                if (leftLength > 0) {
                    packageLength++;
                    leftLength--;
                }
                System.out.println("pos: " + pos + "  endpos: " + packageLength);
                service.execute(new BigFileDownloadRunnable(pos, packageLength, file, endpoint, accessKey, secretKey, bucketName, downloadFilePath));
                pos = packageLength;
                packageLength = packageLength + end;
            }
            //等待其他线程结束后继续向下执行
            try {
                latch.await();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //关闭线程池
            service.shutdown();
        } catch (AmazonServiceException | FileNotFoundException e) {
            // 服务端错误
            e.printStackTrace();
        } finally {
            if (file != null) {
                try {
                    file.close();
                } catch (Exception e) {

                }
            }
        }
        return filename;

    }

}

其实也比较好理解 首先创建一个线程 用来分片下载
然后是主代码
方法入参 分别是 对象平台的一些参数 只有随后一个是存储平台地址 其他平台可自行更改
然后就是做一个本地缓存用的假文件
接着到 异常里
先做存储平台的链接,然后获取要下载文件的大小
获取大小后 更具线程数量 自行计算启动多少个线程来下载 我这里直接给的100 然后使用for进行线程的启动 启动后进入线程等待 全执行完之后 可正常生成文件,

然后就是线程使用的分片下载工具类


/**
 * .
 *
 * @ClassName: BigFileDownloadRunnable
 * @Description:
 * @Author: LXT
 * @Date: 2021/12/27 13:19
 */

class BigFileDownloadRunnable implements Runnable {

    private String endpoint;

    private String accessKey;

    private String secretKey;

    private String bucketName;

    private long from;

    private long end;

    private RandomAccessFile file;

    private String downloadFilePath;

    public BigFileDownloadRunnable(long from, long end, RandomAccessFile file,
                                   String endpoint, String accessKey, String secretKey, String bucketName,String downloadFilePath) {
        this.from = from;
        this.end = end;
        this.file = file;
        this.endpoint = endpoint;
        this.accessKey = accessKey;
        this.secretKey = secretKey;
        this.bucketName = bucketName;
        this.downloadFilePath = downloadFilePath;
    }

    public void run() {

        String filename = downloadFilePath;
        S3Object objectPortion = null;
        InputStream input = null;
        BufferedInputStream buffer = null;
        try {
            AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
            ClientConfiguration clientConfig = new ClientConfiguration();
            clientConfig.setProtocol(Protocol.HTTP);
            clientConfig.setConnectionTimeout(90000000);
            clientConfig.setMaxConnections(1000);
            AmazonS3 s3Client = new AmazonS3Client(credentials, clientConfig);
            s3Client.setEndpoint(endpoint);
            //获取对象指定范围的流写入文件
            GetObjectRequest rangeObjectRequest = new GetObjectRequest(bucketName, filename).withRange(from, end);
            objectPortion = s3Client.getObject(rangeObjectRequest);

            input = objectPortion.getObjectContent();
            buffer = new BufferedInputStream(input);

            byte[] buf = new byte[1024];
            int len;
            long start = this.from;
            long stop = this.end;
            for (; ; ) {
                if ((len = buffer.read(buf)) == -1) {
                    break;
                }
                synchronized (file) {
                    file.seek(from);
                    file.write(buf, 0, len);
                }
                from += len;
            }
            System.out.println("文件片段 " + start + "~" + stop + "下载完成");
            new BigFileDownload().latch.countDown();//线程结束计数器减1
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (buffer != null) {
                try {
                    buffer.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (objectPortion != null) {
                try {
                    objectPortion.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

因为线程分段 也需要去请球平台 所以就将平台的数据一并传输过来

然后线程内run 大概意思 就是 我拿到了平台链接 然后又有文件的存错位置 还有我需要下载的片段位置,然后就是正常下载过程

由于大文件比较大 链接时间设置的相对较长一点,

一小点的进步都是对开发者的激励,记录一下,分享给大家。有用记得点♥

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值